web-dev-qa-db-ja.com

それを反復しながら一般的なリストから要素を削除する方法?

私はより良い pattern を探しています。それぞれが処理される必要がある要素のリストを操作し、結果に応じてリストから削除されます。

.Remove(element)内でforeach (var element in X)を使用することはできません(Collection was modified; enumeration operation may not execute.例外が発生するため)。また、for (int i = 0; i < elements.Count(); i++)および.RemoveAt(i)は、iに対するコレクション内の現在位置を混乱させるため使用できません。

これを行うためのエレガントな方法はありますか?

393

Forループを使用して、リストを逆に繰り返します。

for (int i = safePendingList.Count - 1; i >= 0; i--)
{
    // some code
    // safePendingList.RemoveAt(i);
}

例:

var list = new List<int>(Enumerable.Range(1, 10));
for (int i = list.Count - 1; i >= 0; i--)
{
    if (list[i] > 5)
        list.RemoveAt(i);
}
list.ForEach(i => Console.WriteLine(i));

代わりに、 RemoveAllメソッド を述語とともに使用して、次のことをテストできます。

safePendingList.RemoveAll(item => item.Value == someValue);

これを示す簡単な例を示します。

var list = new List<int>(Enumerable.Range(1, 10));
Console.WriteLine("Before:");
list.ForEach(i => Console.WriteLine(i));
list.RemoveAll(i => i > 5);
Console.WriteLine("After:");
list.ForEach(i => Console.WriteLine(i));
637
Ahmad Mageed

単純で直接的な解決策:

コレクションで標準のfor-loop running backwards を使用し、RemoveAt(i)で要素を削除します。

82
Jan

コレクションを反復しながらCollectionから要素を削除する場合は、最初に逆反復を頭に入れておく必要があります。

幸いなことに、不必要な入力を含み、エラーを起こしやすいforループを書くよりも、より洗練された方法があります。

ICollection<int> test = new List<int>(new int[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10});

foreach (int myInt in test.Reverse<int>())
{
    if (myInt % 2 == 0)
    {
        test.Remove(myInt);
    }
}
63
jedesah
 foreach (var item in list.ToList()) {
     list.Remove(item);
 }

あなたのリスト(またはLINQクエリの結果)に".ToList()"を追加すると、恐ろしい "Collection"を修正せずに "list"から "item"を直接削除することができます。 not execute。 "エラー。コンパイラは" list "のコピーを作成するので、安全に配列を削除できます。

このパターンはあまり効率的ではありませんが、それは自然な感じであり、ほとんどすべての状況に対して十分に柔軟です。各 "項目"をDBに保存してリストから削除したい場合など) DBの保存が成功した場合のみ.

49
Greg Little

総称リストでToArray()を使用すると、総称リストでRemove(item)を実行できます。

        List<String> strings = new List<string>() { "a", "b", "c", "d" };
        foreach (string s in strings.ToArray())
        {
            if (s == "b")
                strings.Remove(s);
        }
20

エレメントを削除しようとするのではなく、doする要素を選択しますdo n't 欲しいです。これは、要素を削除するよりもはるかに簡単です(そして一般的にも効率的です)。

var newSequence = (from el in list
                   where el.Something || el.AnotherThing < 0
                   select el);

以下のMichael Dillonが残したコメントへの応答として、これをコメントとして投稿したかったのですが、とにかく長すぎておそらく答えに含めるのに便利です。

個人的に、アイテムを1つずつ削除することはありません。削除が必要な場合は、RemoveAllを呼び出します。これは、述語を取り、内部配列を1回だけ再配置しますが、RemoveArray.Copy削除するすべての要素に対する操作。 RemoveAllは非常に効率的です。

また、リストを逆方向に反復する場合、削除する要素のインデックスが既にあるので、RemoveAtを最初にトラバースするため、Removeを呼び出す方がはるかに効率的です。削除しようとしている要素のインデックスを見つけるためのリスト。ただし、すでにそのインデックスはわかっています。

全体として、for-loopでRemoveを呼び出す理由は見当たりません。理想的には、可能な場合は、上記のコードを使用して必要に応じてリストから要素をストリーミングし、2番目のデータ構造をまったく作成する必要がないようにします。

20
JulianR

この質問で説明されているように、.ToList()を使うことはあなたのリストのコピーを作るでしょう: ToList() - それは新しいリストを作成しますか?

ToList()を使用すると、実際にコピーを反復処理しているので、元のリストから削除できます。

foreach (var item in listTracked.ToList()) {    

        if (DetermineIfRequiresRemoval(item)) {
            listTracked.Remove(item)
        }

     }
18
StuartQ

どの項目を削除するかを決定する関数に副作用がなく、項目を変更しない場合(純粋な関数です)、単純で効率的な(線形時間)解は次のとおりです。

list.RemoveAll(condition);

副作用があるならば、私は以下のようなものを使うでしょう:

var toRemove = new HashSet<T>();
foreach(var item in items)
{
     ...
     if(condition)
          toRemove.Add(item);
}
items.RemoveAll(toRemove.Contains);

ハッシュが良好であれば、これはまだ線形時間です。しかし、ハッシュセットのためにメモリ使用量が増えます。

最後に、あなたのリストがIList<T>ではなくList<T>だけであるなら、私は この特別なforeachイテレータをどうやってやればいいですか? への私の答えを提案します。他の多くの答えの2次実行時間と比較して、これはIList<T>の典型的な実装を与えられた線形実行時間を持つでしょう。

12
CodesInChaos

あなたが使用することができる条件で任意の削除が行われるように

list.RemoveAll(item => item.Value == someValue);
10
Ahmad
List<T> TheList = new List<T>();

TheList.FindAll(element => element.Satisfies(Condition)).ForEach(element => TheList.Remove(element));
9

Foreachを使うことはできませんが、アイテムを削除するときに、順方向に繰り返しループインデックス変数を管理することができます。

for (int i = 0; i < elements.Count; i++)
{
    if (<condition>)
    {
        // Decrement the loop counter to iterate this index again, since later elements will get moved down during the remove operation.
        elements.RemoveAt(i--);
    }
}

一般に、これらの手法はすべて、反復されるコレクションの動作に依存していることに注意してください。ここに示されたテクニックは標準のList(T)でうまくいくでしょう。 (独自のコレクションクラスとイテレータを書くことで、 - foreachループ中にアイテムを削除することができます。)

6
yoyo

リストを反復処理しながらリストでRemoveまたはRemoveAtを使用することは、ほとんど常にやることが間違っているであるため、意図的に困難にされています。巧妙なトリックでうまく動くかもしれませんが、非常に遅くなります。 Removeを呼び出すたびに、削除したい要素を見つけるためにリスト全体をスキャンする必要があります。 RemoveAtを呼び出すたびに、後続の要素を1つ左に移動する必要があります。そのため、RemoveまたはRemoveAtを使用するソリューションでは、2次時間 O(n²) が必要になります。

可能であればRemoveAllを使用してください。そうでなければ、次の pattern がリストをフィルタリングします インプレース 線形時間で、 O(n)

// Create a list to be filtered
IList<int> elements = new List<int>(new int[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10});
// Filter the list
int kept = 0;
for (int i = 0; i < elements.Count; i++) {
    // Test whether this is an element that we want to keep.
    if (elements[i] % 3 > 0) {
        // Add it to the list of kept elements.
        elements[kept] = elements[i];
        kept++;
    }
}
// Unfortunately IList has no Resize method. So instead we
// remove the last element of the list until: elements.Count == kept.
while (kept < elements.Count) elements.RemoveAt(elements.Count-1);
4
bcmpinc

I wish 「パターン」は次のようなものです。

foreach( thing in thingpile )
{
    if( /* condition#1 */ )
    {
        foreach.markfordeleting( thing );
    }
    elseif( /* condition#2 */ )
    {
        foreach.markforkeeping( thing );
    }
} 
foreachcompleted
{
    // then the programmer's choices would be:

    // delete everything that was marked for deleting
    foreach.deletenow(thingpile); 

    // ...or... keep only things that were marked for keeping
    foreach.keepnow(thingpile);

    // ...or even... make a new list of the unmarked items
    others = foreach.unmarked(thingpile);   
}

これはコードをプログラマーの頭脳の中で進行するプロセスと整合させるでしょう。

3
warrens

保持したくない要素を除外したLINQクエリからリストを再割り当てします。

list = list.Where(item => ...).ToList();

リストが非常に大きい場合を除き、これを実行してもパフォーマンスに大きな問題はないはずです。

3
foreach(var item in list.ToList())

{

if(item.Delete) list.Remove(item);

}

最初のものから全く新しいリストを作成するだけです。まったく新しいリストを作成するのであれば、「正しい」ではなく「簡単」と言うのがおそらく以前の方法よりも優れたパフォーマンスであると思います(ベンチマークに悩まされていません)。実体から実体への制限。

for(i = list.Count()-1;i>=0;i--)

{

item=list[i];

if (item.Delete) list.Remove(item);

}

このようにして、単純な古いForループでリストを逆方向に循環します。コレクションのサイズが変更された場合、これを前方に行うことは問題になる可能性がありますが、後方に移動することは常に安全なはずです。

2
Lijo

繰り返しながらリストから項目を削除する最善の方法は、RemoveAll()を使用することです。しかし、人々が書いた主な関心事は、ループの中でいくつかの複雑なことをしなければならないこと、および/または複雑な比較ケースがあることです。

解決策は依然としてRemoveAll()を使用することですが、この表記法を使用します。

var list = new List<int>(Enumerable.Range(1, 10));
list.RemoveAll(item => 
{
    // Do some complex operations here
    // Or even some operations on the items
    SomeFunction(item);
    // In the end return true if the item is to be removed. False otherwise
    return item > 5;
});
2

predicate が要素のブール型プロパティであると仮定すると、それが真であれば、その要素は削除されるべきです。

        int i = 0;
        while (i < list.Count())
        {
            if (list[i].predicate == true)
            {
                list.RemoveAt(i);
                continue;
            }
            i++;
        }
2
roeesha

C#では、簡単な方法として、削除したいものにマークを付けてから、新しいリストを作成して繰り返します。

foreach(var item in list.ToList()){if(item.Delete) list.Remove(item);}  

あるいはもっと単純にlinqを使うこともできます。

list.RemoveAll(p=>p.Delete);

しかし、他のタスクやスレッドが削除に忙しいと同時に同じリストにアクセスするかどうかを検討する価値があります。代わりにConcurrentListを使用してください。

0
andrew pate

私は私がすべてのnを削除しなければならなかった同じような状況に自分自身を見つけました番目 与えられたList<T>の要素。

for (int i = 0, j = 0, n = 3; i < list.Count; i++)
{
    if ((j + 1) % n == 0) //Check current iteration is at the nth interval
    {
        list.RemoveAt(i);
        j++; //This extra addition is necessary. Without it j will wrap
             //down to zero, which will throw off our index.
    }
    j++; //This will always advance the j counter
}
0

私のアプローチは、最初にインデックスのリストを作成することです。これは削除されるはずです。その後、インデックスをループして、初期リストから項目を削除します。これはこんな感じです:

var messageList = ...;
// Restrict your list to certain criteria
var customMessageList = messageList.FindAll(m => m.UserId == someId);

if (customMessageList != null && customMessageList.Count > 0)
{
    // Create list with positions in Origin list
    List<int> positionList = new List<int>();
    foreach (var message in customMessageList)
    {
        var position = messageList.FindIndex(m => m.MessageId == message.MessageId);
        if (position != -1)
            positionList.Add(position);
    }
    // To be able to remove the items in the Origin list, we do it backwards
    // so that the order of indices stays the same
    positionList = positionList.OrderByDescending(p => p).ToList();
    foreach (var position in positionList)
    {
        messageList.RemoveAt(position);
    }
}
0
testing

リストから項目を削除するためのコストは、削除する項目の次の項目の数に比例します。アイテムの前半が削除の対象となる場合、アイテムを個別に削除することに基づくすべてのアプローチでは、約N * N/4のアイテムコピー操作を実行する必要があり、リストが大きい場合は非常に高価になります。 。

より高速な方法は、リストをスキャンして削除する最初の項目がある場合はそれを見つけ、その時点から先に各項目をその項目が属する場所にコピーすることです。一旦これが行われると、R個のアイテムが保持されるべきである場合、リスト内の最初のR個のアイテムはそれらのR個のアイテムとなり、削除を必要とする全てのアイテムは最後になる。これらのアイテムが逆の順序で削除された場合、システムはそれらのいずれもコピーする必要がなくなるので、リストに最初のFのすべてを含むR個のアイテムが保持されているN個のアイテムがある場合、 RFアイテムをコピーし、リストを1アイテムNRだけ縮小します。すべて線形時間.

0
supercat

繰り返しているリストをコピーします。それからコピーから削除して、オリジナルを繰り返します。逆方向に進むことは混乱を招き、並行してループするときうまくいかない。

var ids = new List<int> { 1, 2, 3, 4 };
var iterableIds = ids.ToList();

Parallel.ForEach(iterableIds, id =>
{
    ids.Remove(id);
});
0