简体   繁体   English

从List中删除多余的项目时,枚举器陷入无限循环

[英]Enumerator stuck in endless loop when removing excess items from a List

I have a script that takes an int[] array, converts it to a list and removes all further occurrences of the integers that already occurred at least 2 times. 我有一个脚本,它接受一个int[]数组,将其转换为一个列表,并删除已经出现至少2次的整数的所有进一步出现。 The problem I have is that when it gets into the loop where I am checking the count of each integers occurrences, I am getting stuck in a loop. 我遇到的问题是,当它进入循环,我正在检查每个整数出现的计数时,我陷入了循环。

EDIT: "What I left out was that the list has to remain in its original order so that excess numbers are removed from top down. Sorry if that confused those who already answered! 编辑:“我遗漏的是,列表必须保持其原始顺序,以便从上到下删除多余的数字。抱歉,如果那些已经回答的人混淆了!

I thought that the changed number of the occursintegerOccurrence would act as a change of count for the while loop. 我认为, occursintegerOccurrence的更改次数将作为while循环的计数更改。

Any ideas on what I'm missing here? 关于我在这里缺少什么的想法? Aside from any discernible skill. 除了任何可辨别的技能。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Remoting.Messaging;

public class Kata
{
    public static void Main()
    {
       int[] arr = new int[] {1, 2, 1, 4, 5, 1, 2, 2, 2};
        int occurrenceLimit = 2;
       var intList = arr.ToList();

        for (int i = 0; i < intList.Count; i++)
        {
            var occursintegerOccurrence = intList.Count(n => n == occurrenceLimit);

            do
            {
                occursintegerOccurrence = intList.Count(n => n == occurrenceLimit);
                foreach (var x in intList)
                {
                    Console.WriteLine(x);
                    intList.Remove(intList.LastIndexOf(occurrenceLimit));
                    // Tried changing the count here too
                    occursintegerOccurrence = intList.Count(n => n == occurrenceLimit);
                }
            } while (occursintegerOccurrence > occurrenceLimit);
        }
    }
}

Here's a fairly concise version, assuming that you want to remove all instances of integers with a count in excess of 2, leaving the remainder of the bag in its original sequence, with preference to retention traversing from left to right: 这是一个相当简洁的版本,假设您要删除计数超过2的所有整数实例,将其余部分保留为原始序列,优先选择从左到右的保留:

int[] arr = new int[] {1, 2, 1, 4, 5, 1, 2, 2, 2};
var ints = arr.Select((n, idx) => new {n, idx})
               .GroupBy(x => x.n)
               .SelectMany(grp => grp.Take(2))
               .OrderBy(x => x.idx)
               .Select(x => x.n)
               .ToList();

Result: 结果:

1, 2, 1, 4, 5, 2 1,2,1,4,5,2

It works by using the index overload of Select to project an anonymous Tuple and carrying through the original order to allow re-ordering at the end. 它的工作原理是使用Select的索引重载来投影匿名元组并执行原始顺序以允许在最后重新排序。

The cause of the endless loop is the line 无限循环的原因是线

 intList.Remove(intList.LastIndexOf(occurrenceLimit));

..you are removing the value equals to the last occurence in the list of the occurrenceLimit value(=2), that it is "8" (the last index of the array counting from 0). ..你要删除的值等于occurrenceLimit值列表中最后一次occurrenceLimit值(= 2),它是“8”(数组的最后一个索引从0开始计算)。

Since "8" it isn't present in the list, you don't remove anything and the loop permanence test doesn't ever change and so it is always verified and the loop never ends.. 由于“8”它不存在于列表中,因此您不会删除任何内容,并且循环持久性测试不会发生变化,因此它始终被验证并且循环永远不会结束。

This method works for any values of occurrenceLimit but I think that the solution of StuartLC is better.. 此方法适用于occurrenceLimit任何值,但我认为StuartLC的解决方案更好..

int[] arr = new int[] { 1, 2, 1, 4, 5, 1, 2, 2, 2 };
int?[] arr2 = new int?[arr.Length];
arr2.ToList().ForEach(i => i = null);
int occurrenceLimit = 2;

var ints = arr.GroupBy(x => x).Select(x => x.Key).ToList();

ints.ForEach(i => {
   int ndx = 0;
   for (int occ = 0; occ < occurrenceLimit; occ++){
        ndx = arr.ToList().IndexOf(i, ndx);
        if (ndx < 0) break;
        arr2[ndx++] = i;
   }
});

List<int?> intConverted = arr2.ToList();
intConverted.RemoveAll(i => i.Equals(null));

this may help you 这可能对你有帮助

     namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            int[] arr = new int[] { 1, 2, 1, 4, 5, 1, 2, 2, 2 };
            int occurrenceLimit = 2;
            var newList = new List<Vm>();


            var result=new List<Vm>();

            for (int i = 0; i < arr.Length; i++)
            {
                var a = new Vm {Value = arr[i], Index = i};
                result.Add(a);
            }

            foreach (var item in result.GroupBy(x => x.Value))
            {
                newList.AddRange(item.Select(x => x).Take(occurrenceLimit));
            }

            Console.WriteLine(string.Join(",",newList.OrderBy(x=>x.Index).Select(a=>a.Value)));

            Console.ReadKey();
        }
    }

    public class Vm
    {
        public int Value { get; set; }
        public int Index { get; set; }
    }
}

I did the following: 我做了以下事情:

  1. I created a Vm class with 2 props (Value and Index), in order to save the index of each value in the array. 我创建了一个带有2个道具(值和索引)的Vm类,以便保存数组中每个值的索引。

  2. I goup by value and take 2 ccurence of each values. 我按值计算并且每个值都取2次。

  3. I order the result list base on the initial index. 我根据初始索引对结果列表进行了排序。

It can be done by defining your own enumerator method, which will count already happened occurrences: 可以通过定义自己的枚举器方法来完成,该方法将计算已经发生的事件:

using System;
using System.Collections.Generic;
using System.Linq;
static class Test {
    static IEnumerable<int> KeepNoMoreThen(this IEnumerable<int> source, int limit) {
        Dictionary<int, int> counts = new Dictionary<int, int>();
        foreach(int current in source) {
            int count;
            counts.TryGetValue(current, out count);
            if(count<limit) {
                counts[current]=count+1;
                yield return current;
            }
        }
    }
    static void Main() {
        int[] arr = new int[] { 1, 2, 1, 4, 5, 1, 2, 2, 2 };
        int occurrenceLimit = 2;
        List<int> result = arr.KeepNoMoreThen(occurrenceLimit).ToList();
        result.ForEach(Console.WriteLine);
    }
}
var removal = arr.GroupBy (a =>a ).Where (a =>a.Count()>2).Select(a=>a.Key).ToArray();

var output = arr.Where (a =>!removal.Contains(a)).ToList();

removal is an array of the items which appear more than twice. removal是一个出现两次以上的项目数组。

output is the original list with those items removed. output是删除了这些项目的原始列表。

[Update -- Just discovered that this handles the problem as originally specified, not as later clarified) [更新 - 刚刚发现它处理了最初指定的问题,而不是后来澄清的)

A single pass over the input array maintaining occurrence count dictionary should do the job in O(N) time: 在输入数组上单次传递维护出现计数字典应该在O(N)时间内完成工作:

int[] arr = new int[] { 1, 2, 1, 4, 5, 1, 2, 2, 2 };
int occurrenceLimit = 2;
var counts = new Dictionary<int, int>();
var resilt = arr.Where(n =>
{
    int count;
    if (counts.TryGetValue(n, out count) && count >= occurrenceLimit) return false;
    counts[n] = ++count;
    return true;
}).ToList();

Your code is stuck in an infinite loop because you are using List.Remove() , and the Remove() method removes an item by matching against the item you pass in. But you are passing in a list index instead of a list item , so you are getting unintended results. 你的代码是停留在一个无限循环,因为你正在使用List.Remove()Remove()方法,通过对您通过在项目配套删除的项目。但是,你传递一个列表索引 ,而不是一个列表 ,所以你得到了意想不到的结果。 What you want to use is List.RemoveAt() , which removes an item by matching against the index . 你想要使用的是List.RemoveAt() ,它通过匹配索引来删除项目。

So your code is stuck in an infinite loop because intList.LastIndexOf(occurrenceLimit) is returning 8 , then Remove() looks for the item 8 in the list, but it doesn't find it so it returns false and your code continues to run. 所以你的代码陷入无限循环,因为intList.LastIndexOf(occurrenceLimit)返回8 ,然后Remove()在列表中查找项目 8 ,但它找不到它所以它返回false并且你的代码继续运行。 Changing this line: 改变这一行:

intList.Remove(intList.LastIndexOf(occurrenceLimit));

to

intList.RemoveAt(intList.LastIndexOf(occurrenceLimit));

will "fix" your code and it will no longer get stuck in an infinite loop. 将“修复”你的代码,它将不再陷入无限循环。 It would then have the expected behavior of throwing an exception because you are modifying a collection that you are iterating through in a foreach . 然后,它将具有抛出异常的预期行为,因为您正在修改在foreach中迭代的集合。

As for your intended solution, I have rewritten your code with some changes, but keeping most of your code there instead of rewriting it entirely using LINQ or other magic. 至于你想要的解决方案,我已经用一些更改重写了你的代码,但保留了你的大部分代码而不是使用LINQ或其他魔法完全重写它。 You had some issues: 你有一些问题:

1) You were counting the number of times occurenceLimit was found in the list, not the number of times an item was found in the list. 1)您计算在列表中找到occurenceLimit的次数,而不是列表中找到项目的次数。 I fixed this by comparing against intList[i] . 我通过与intList[i]进行比较来修复此问题。

2) You were using Remove() instead of RemoveAt(). 2)您使用的是Remove()而不是RemoveAt()。

3) Your foreach and do while need some work. 3)你的foreachdo while需要一些工作的do while I went with a while to simplify the initial case, and then used a for loop so I can modify the list (you cannot modify a list that you are iterating over in a foreach ). 我花了一段while来简化初始情况,然后使用for循环,这样我就可以修改列表(你不能修改你在foreach中迭代的列表)。 In this for loop I iterate to the number of occurences - occurenceLimit to remove all but the first occurenceLimit number of them -- your initial logic was missing this and if your code worked as intended you would have removed every single one. 在这for循环迭代我对出现次数的数量- occurenceLimit删除所有,但第一occurenceLimit他们的号码-您最初的逻辑缺失这一点,如果你的代码工作按预期你会删除每一个。

    static void Main(string[] args)
    {
        int[] arr = new int[] { 1, 2, 1, 4, 5, 1, 2, 2, 2 };
        int occurrenceLimit = 2;
        var intList = arr.ToList();
        // Interestingly, this `.Count` property updates during the for loop iteration,
        // so even though we are removing items inside this `for` loop, we do not run off the
        // end of the list as Count is constantly updated.
        // Doing `var count = intList.Count`, `for (... i < count ...)` would blow up.
        for (int i = 0; i < intList.Count; i++)
        {
            // Find the number of times the item at index `i` occurs
            int occursintegerOccurrence = intList.Count(n => n == intList[i]);

            // If `occursintegerOccurrence` is greater than `occurenceLimit`
            // then remove all but the first `occurrenceLimit` number of them
            while (occursintegerOccurrence > occurrenceLimit)
            {
                // We are not enumerating the list, so we can remove items at will.
                for (var ii = 0; ii < occursintegerOccurrence - occurrenceLimit; ii++)
                {
                    var index = intList.LastIndexOf(intList[i]);
                    intList.RemoveAt(index);
                }

                occursintegerOccurrence = intList.Count(n => n == intList[i]);
            }
        }

        // Verify the results
        foreach (var item in intList)
        {
            Console.Write(item + " ");
        }

        Console.WriteLine(Environment.NewLine + "Done");
        Console.ReadLine();
    }

Here's a pretty optimal solution: 这是一个非常优化的解决方案:

var list = new List<int> { 1, 2, 1, 4, 5, 1, 2, 2, 2 };
var occurrenceLimit = 2;

list.Reverse(); // Reverse list to make sure we remove LAST elements

// We will store count of each element's occurence here
var counts = new Dictionary<int, int>();

for (int i = list.Count - 1; i >= 0; i--)
{
    var elem = list[i];
    if (counts.ContainsKey(elem)) // If we already faced this element we increment the number of it's occurencies
    {
        counts[elem]++;
        if (counts[elem] > occurrenceLimit) // If it occured more then 2 times we remove it from the list
            list.RemoveAt(i);
    }        
    else
        counts.Add(elem, 1); // We haven't faced this element yet so add it to the dictionary with occurence count of 1
}

list.Reverse(); // Again reverse list

The key feature with list is that you have to traverse it backwards to have a possibility to remove items. 列表的关键功能是您必须向后遍历它才有可能删除项目。 When you traverse it as usual it will throw you an exception that explains that the list cannot modified. 当您像往常一样遍历它时,它会抛出一个异常,说明列表无法修改。 But when you are going backwards you can remove elements as you wish as this won't affect your further operations. 但是当你向后退时,你可以随意删除元素,因为这不会影响你的进一步操作。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM