枚举器在从列表中删除多余的项时陷入无限循环
本文关键字:无限循环 多余 删除 列表 枚举 | 更新日期: 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; }
}
}
我做了以下工作:
我创建了一个带有 2 个 props(值和索引)的 Vm 类,以便保存数组中每个值的索引。
我按值计算,每个值取 2 个。
我根据初始索引对结果列表进行排序。
这可以通过定义您自己的枚举器方法来完成,该方法将计算已经发生的事件:
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)你的foreach
和do 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
列表的主要功能是您必须向后遍历它才能删除项目。当您像往常一样遍历它时,它会抛出一个异常,说明列表无法修改。但是当你倒退时,你可以根据需要删除元素,因为这不会影响你的进一步操作。