枚举器在从列表中删除多余的项时陷入无限循环

本文关键字:无限循环 多余 删除 列表 枚举 | 更新日期: 2023-09-27 17:57:03

我有一个脚本,它采用一个int[]数组,将其转换为列表并删除至少出现至少 2 次的整数的所有进一步出现。我遇到的问题是,当它进入我检查每个整数出现的计数的循环时,我陷入了循环。

编辑:"我遗漏的是列表必须保持其原始顺序,以便从上到下删除多余的数字。对不起,如果这让那些已经回答的人感到困惑!

我认为更改的occursintegerOccurrence数将充当 while 循环计数的变化。

关于我在这里错过了什么的任何想法?除了任何可识别的技能。

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);
        }
    }
}

枚举器在从列表中删除多余的项时陷入无限循环

这是一个相当简洁的版本,假设您要删除计数超过 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();

结果:

1, 2,

1, 4, 5, 2

它的工作原理是使用 Select 的索引重载来投影匿名元组,并执行原始顺序以允许在末尾重新排序。

无限循环的原因是线

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

..您正在删除的值等于 occurrenceLimit 值 (=2) 列表中最后一次出现的值,即它是"8"(数组的最后一个索引,从 0 开始计数)。

由于"8"它不在列表中,因此您不会删除任何内容,并且循环持久性测试永远不会更改,因此它始终经过验证并且循环永远不会结束。

这种方法适用于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));
这可能会

对你有所帮助

     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; }
    }
}

我做了以下工作:

  1. 我创建了一个带有 2 个 props(值和索引)的 Vm 类,以便保存数组中每个值的索引。

  2. 我按值计算,每个值取 2 个。

  3. 我根据初始索引对结果列表进行排序。

这可以通过定义您自己的枚举器方法来完成,该方法将计算已经发生的事件:

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是出现两次以上的项目数组。

output是删除了这些项目的原始列表。

[更新 - 刚刚发现这处理了最初指定的问题,而不是后来澄清的问题)

对维护出现计数字典的输入数组进行单次传递应在 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();

您的代码陷入无限循环,因为您使用的是 List.Remove() ,并且 Remove() 方法通过匹配您传入的项来删除项目。但是您传递的是列表索引而不是列表,因此会得到意外的结果。要使用的是 List.RemoveAt() ,它通过匹配索引来删除项目。

因此,您的代码陷入无限循环,因为intList.LastIndexOf(occurrenceLimit)返回 8 ,然后Remove()在列表中查找8,但它找不到它,因此它返回false并且您的代码继续运行。更改此行:

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

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

将"修复"您的代码,它将不再陷入无限循环。然后,它将具有引发异常的预期行为,因为您正在修改在foreach中循环访问的集合。

至于您的预期解决方案,我已经通过一些更改重写了您的代码,但将您的大部分代码保留在那里,而不是使用 LINQ 或其他魔法完全重写它。您遇到了一些问题:

1)您计算的是occurenceLimit在列表中找到的次数,而不是在列表中找到项目的次数。我通过与intList[i]进行比较来解决此问题。

2)你使用的是Remove()而不是RemoveAt()。

3)你的foreachdo while需要一些工作。我用了一个while来简化初始情况,然后使用for循环,以便我可以修改列表(您不能修改在foreach中迭代的列表)。在这个for循环中,我迭代到出现次数 - 出现限制删除除前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();
    }

这是一个非常理想的解决方案:

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

列表的主要功能是您必须向后遍历它才能删除项目。当您像往常一样遍历它时,它会抛出一个异常,说明列表无法修改。但是当你倒退时,你可以根据需要删除元素,因为这不会影响你的进一步操作。