10

まず、明らかな理由により、これをすぐに使用できるわけではないことを私は知っています。

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

上で切り取られたものは、私が今まで見た中で最も恐ろしいものの 1 つです。それで、どうやってそれを達成しますか?を使用してリストを逆方向に繰り返すこともできますがfor、私はこの解決策も好きではありません。

私が疑問に思っているのは、現在のリストからIEnumerableを返すメソッド/拡張機能はありますか?フローティングコピーのようなものですか? LINQ にはまさにこれを行う拡張メソッドが多数ありますが、フィルタリング (where, take...) など、常に何かを行う必要があります。

私は次のようなことを楽しみにしています:

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

それほど高速ではありませんが (明らかに)、安全であり、まさに私が意図したとおりです。

4

6 に答える 6

27

リストから複数の要素を 1 つずつ削除することは、リストの実装方法に起因する C# アンチパターンです。

もちろん、(foreach の代わりに) for ループで実行できます。または、リストのコピーを作成することで実行できます。しかし、これを行うべきではない理由がここにあります。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--);

これら 2 つはそれぞれ 5 ミリ秒と 2 ミリ秒かかります。

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

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

これは、リストから要素を削除すると、実際には配列から削除されるためです。これは、削除された要素のの各要素を 1 桁左にシフトする必要があるため、O(N) 時間です。平均すると、これは N/2 要素になります。

Remove(element) も、削除する前に要素を見つける必要があります。したがって、Remove(element) は実際には常に N ステップ (要素elementindexを見つけるためのN - elementindexステップ、要素を削除するためのステップ) を必要とし、合計で N ステップになります。

RemoveAt(index) は要素を見つける必要はありませんが、基になる配列をシフトする必要があるため、平均して、RemoveAt は N/2 ステップです。

最大 N 個の要素を削除するため、最終結果はどちらにしても O(N^2) の複雑さになります。

代わりに、O(N) 時間でリスト全体を変更する Linq を使用するか、独自のロールを作成する必要がありますが、ループ内で Remove (または RemoveAt) を使用しないでください。

于 2013-06-21T11:19:19.627 に答える
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())

于 2013-06-21T10:59:50.087 に答える
3

これには LINQ 拡張メソッドは使用しません。次のように、新しいリストを明示的に作成できます。

foreach(string item in new List<string>(myListOfStrings)) {
    myListOfStrings.Remove(item);
}
于 2013-06-21T11:01:07.780 に答える