在迭代时从列表中删除/添加项目

时间:2013-06-21 10:58:13

标签: c# .net extension-methods

首先,我知道由于显而易见的原因,开箱即用是不可能的。

foreach(string item in myListOfStrings) {
    myListOfStrings.Remove(item);
}

上面的剪辑是我见过的最可怕的事情之一。那么,你如何实现呢?你可以使用for向后遍历列表,但我也不喜欢这个解决方案。

我想知道的是:是否有一个方法/扩展从当前列表返回IEnumerable,类似浮动副本? LINQ有很多扩展方法可以做到这一点,但你总是要对它做一些事情,比如过滤(在哪里,拿......)。

我期待着这样的事情:

foreach(string item in myListOfStrings.Shadow()) {
   myListOfStrings.Remove(item);
}

其中.Shadow()是:

public static IEnumerable<T> Shadow<T>(this IEnumerable<T> source) {
    return new IEnumerable<T>(source);
    // or return source.Copy()
    // or return source.TakeAll();
}

示例

foreach(ResponseFlags flag in responseFlagsList.Shadow()) {
    switch(flag) {
        case ResponseFlags.Case1:
            ...
        case ResponseFlags.Case2:
            ...
    }
    ...
    this.InvokeSomeVoidEvent(flag)
    responseFlagsList.Remove(flag);
}

解决方案

这就是我解决它的方式,它就像一个魅力:

public static IEnumerable<T> Shadow<T>(this IEnumerable<T> source) where T: new() {
    foreach(T item in source)
        yield return item;
}

这不是那么超级快(显然),但它是安全的,而且正是我打算做的。

6 个答案:

答案 0 :(得分:25)

由于列表的实现方式,逐个从列表中删除多个元素是C#反模式。

当然,可以使用for循环(而不是foreach)来完成。或者可以通过制作列表的副本来完成。但这就是不应该完成的原因。在100000个随机整数的列表中,我的机器需要2500毫秒:

       foreach (var x in listA.ToList())
            if (x % 2 == 0)
                listA.Remove(x);

这需要1250毫秒:

        for (int i = 0; i < listA.Count; i++)
            if (listA[i] % 2 == 0)
                listA.RemoveAt(i--);

虽然这两个分别需要5和2毫秒:

        listB = listB.Where(x => x % 2 != 0).ToList();

        listB.RemoveAll(x => x % 2 == 0);

这是因为当您从列表中删除元素时,实际上是从数组中删除,这是O(N)时间,因为您需要在每个元素之后移动 删除的元素左侧一个位置。平均而言,这将是N / 2个元素。

删除(元素)还需要在删除之前找到该元素。所以删除(元素)实际上总是需要N步 - elementindex步骤来找到元素,N - elementindex步骤来删除它 - 总共N步。

RemoveAt(index)不必找到元素,但它仍然必须移动底层数组,所以平均而言,RemoveAt是N / 2步。

最终结果是O(N ^ 2)复杂度,因为你要删除多达N个元素。

相反,您应该使用Linq,它将在O(N)时间内修改整个列表,或者自己滚动,但不应该在循环中使用Remove(或RemoveAt)。

答案 1 :(得分:8)

为什么不这样做:

foreach(string item in myListOfStrings.ToList()) 
{
    myListOfStrings.Remove(item);
}

要创建原始副本并用于迭代,请从现有内容中删除。

如果你真的需要你的扩展方法,你可以创建一些对用户更具可读性的东西,例如:

 public static IEnumerable<T> Shadow<T>(this IEnumerable<T> items)
 {
     if (items == null)
        throw new NullReferenceException("Items cannot be null");

     List<T> list = new List<T>();
     foreach (var item in items)
     {
         list.Add(item);
     }
     return list;
 }

这与.ToList()基本相同。

通话:

foreach(string item in myListOfStrings.Shadow())

答案 2 :(得分:3)

您没有LINQ扩展方法 - 您可以显式创建新列表,如下所示:

foreach(string item in new List<string>(myListOfStrings)) {
    myListOfStrings.Remove(item);
}

答案 3 :(得分:3)

您必须在迭代时创建原始列表的副本,如下所示:

        var myListOfStrings = new List<string>();

        myListOfStrings.Add("1");
        myListOfStrings.Add("2");
        myListOfStrings.Add("3");
        myListOfStrings.Add("4");
        myListOfStrings.Add("5");

        foreach (string item in myListOfStrings.ToList())
        {
            myListOfStrings.Remove(item);
        }

答案 4 :(得分:3)

您的示例会从字符串中删除所有项目,因此它等同于:

myListOfStrings.Clear();

它也相当于:

myListOfStrings.RemoveAll(x => true); // Empties myListOfStrings

但我认为你正在寻找的是一种删除谓词为真的项目的方法 - 这就是RemoveAll()所做的。

所以你可以写,例如:

myListOfStrings.RemoveAll(x => x == "TEST"); // Modifies myListOfStrings

或使用任何其他谓词。

但是,这会更改ORIGINAL列表;如果您只想要删除某些项目的列表副本,您可以使用普通的Linq:

// Note != instead of == as used in Removeall(), 
// because the logic here is reversed.

var filteredList = myListOfStrings.Where(x => x != "TEST").ToList(); 

答案 5 :(得分:0)

接受svinja的回答我确实认为解决这个问题的最有效方法是:

/* if(list.length>0 && public){}*/

{{#checkIf list.length '>' 0 '&&' public '==' true}} <p>condition satisfied</p>{{/checkIf}}

通过删除不必要的总和和减法来改进答案。