web-dev-qa-db-ja.com

繰り返しながらリストにアイテムを削除/追加する

まず、明らかな理由により、これは箱から出してすぐには不可能であることを私は知っています。

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

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

10
Atrotygma

リストから複数の要素を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);

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

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

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

最終的には、最大N個の要素を削除するため、どちらの方法でもO(N ^ 2)の複雑さが生じます。

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

25
svinja

なぜそうしないのですか?

_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())

8
DGibbs

以下のように繰り返しながら、元のリストのコピーを作成する必要があります。

        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);
        }
3
Azhar Khorasany

この例では、文字列からすべての項目が削除されるため、次のようになります。

_myListOfStrings.Clear();
_

また、次と同等です。

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

しかし、あなたが探していると思うのは、述語が真であるアイテムを削除する方法です。これは、RemoveAll()が行うことです。

したがって、たとえば次のように書くことができます。

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

または、他の述語を使用します。

ただし、これにより元のリストが変更されます。特定のアイテムを削除したリストのコピーが必要な場合は、通常のLinqを使用できます。

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

var filteredList = myListOfStrings.Where(x => x != "TEST").ToList(); 
_
3
Matthew Watson

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

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

svinja の答えを拾うこの問題を解決する最も効率的な方法は、次のことを行うことだと思います。

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

不必要な合計と減算を削除することにより、答えが改善されます。

0