繰り返しながらジェネリックリストから要素を削除する方法


451

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

あなたは使用することはできません.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

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

回答:


734

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

19
Javaから来る人にとって、C#のリストは挿入/削除がO(n)であり、インデックスを介した取得がO(1)であるという点でArrayListに似ています。これは従来のリンクリストではありません。古典的なリンクリストが思い浮かぶので、少し残念なことに、C#では「リスト」という単語を使用してこのデータ構造を説明しています。
ジャロッドスミス

79
「リスト」という名前の中に「LinkedList」と書かれたものはありません。Java以外の言語を使用している人は、リンクされたリストになると混乱する可能性があります。
GolezTrol 2014年

5
私は、誰もがREMOVEALLためvb.net同等の構文を望んでいる場合にはその検索A vb.netて、ここで終わった:list.RemoveAll(Function(item) item.Value = somevalue)
pseudocoder

1
また、これは、最小限の変更で、Javaで動作します.size()代わりに.Countし、.remove(i)代わりに.removeAt(i)。賢い-ありがとう!
Autumn Leonard

2
パフォーマンスのテストを少し行いましたがRemoveAll()、逆方向forループの3倍の時間がかかりました。だから私は間違いなくループに固執している、少なくともそれが重要な部分では。
Crusha K.Rool 2016

84

シンプルでわかりやすいソリューション:

コレクションに対して逆方向に実行する標準のforループを使用して、RemoveAt(i)要素を削除します。


2
リストに多くのアイテムが含まれている場合、アイテムを1つずつ削除すること効率的ではないことに注意してください。O(n ^ 2)になる可能性があります。最初の10億個のアイテムがすべて削除されてしまう、20億個のアイテムがあるリストを想像してみてください。削除するたびに、それ以降のすべてのアイテムが強制的にコピーされるため、10億のアイテムがそれぞれ10億回コピーされます。これは、逆の反復によるものではなく、一度に1つずつ削除されるためです。RemoveAllは、各アイテムが最大で1回コピーされるようにし、線形になります。一度に1つずつ削除すると、10億倍遅くなる可能性があります。O(n)対O(n ^ 2)。
Bruce Dawson、

71

コレクションを反復しながらコレクションから要素を削除する場合は、最初にリバースイテレーションが頭に浮かぶはずです。

幸いなことに、不必要なタイピングを含み、エラーが発生しやすい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);
    }
}

7
これは完璧に機能しました。シンプルでエレガントで、コードに最小限の変更を加える必要があります。
スティーブンマクドゥーガル2013

1
これは単なるフリップピンの天才ですか?そして、私は@StephenMacDougallに同意します。これらのC ++のforループを使用する必要はなく、意図したとおりにLINQを使用します。
Piotr Kula 14

5
単純なforeach(int myInt in test.ToList()){if(myInt%2 == 0){test.Remove(myInt);に勝る利点はありません。}}それでもリバースにコピーを割り当てる必要があり、それはハァを導入しますか?瞬間-なぜリバースがあるのですか?
jahav

11
@jedesahはい、Reverse<T>()リストを逆にたどるイテレータを作成しますが、リスト自体と同じサイズの追加のバッファ割り当てます(referencesource.microsoft.com/#System.Core/System/Linq/…)。Reverse<T>元のリストを逆の順序で処理するだけではありません(追加のメモリを割り当てません)。したがって、ToList()Reverse()はどちらも同じメモリ消費量(両方ともコピーを作成)をToList()持ちますが、データには何もしません。ではReverse<int>()、なぜリストが逆転されるのか、どのような理由でなのかと思います。
jahav 2015年

1
@jahav私はあなたの要点を理解しています。の実装がReverse<T>()新しいバッファを作成することはまったくがっかりです。なぜそれが必要なのか理解できません。の基本的な構造によってはEnumerable、少なくとも一部のケースでは、線形メモリを割り当てずに逆反復を実現できるように思えます。
jedesah

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

あなたがいる場合、「().ToList」を追加し、あなたのリストに(またはLINQクエリの結果)、あなたは恐ろしい『なし『リスト』から直接『アイテム』を削除することができ、列挙操作が実行されないことがありコレクションが変更されました。』エラー。コンパイラーは「リスト」のコピーを作成するため、配列を安全に削除できます。

一方で、このパターンは、超効率的ではありません、それは自然な感触を持っているとされ、ほぼどのような状況のために十分な柔軟性。たとえば、各「アイテム」をDBに保存し、DBの保存が成功した場合にのみリストから削除する場合などです。


6
効率が重要でない場合は、これが最良のソリューションです。
IvanP 2016年

2
これも高速で読みやすくなっています。list.RemoveAll(i => true);
サンティアゴアリスティ2016

1
@Greg Little、私はあなたを正しく理解しましたか?ToList()コンパイラを追加すると、コピーされたコレクションを通過しますが、オリジナルから削除しますか?
Pyrejkee

リストに重複するアイテムが含まれていて、リストの後半にあるアイテムのみを削除したい場合は、間違ったアイテムを削除しないのですか?
Tim MB

これはオブジェクトのリストでも機能しますか?
トムエルサファディ

23

あなたは要素を選択しないではなく、あなたが要素削除しようとするよりも、たくありませんしたいが。これは、要素を削除するよりもはるかに簡単です(一般的には効率もよくなります)。

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

以下のMichael Dillonが残したコメントへの応答として、これをコメントとして投稿したかったのですが、長すぎて、とにかく私の回答に含めるのに役立つでしょう:

あなたが必要性の除去を行う場合は個人的に、私が呼び出し、その後、1つずつ項目を削除することがなかったRemoveAllのに対し、述語を取り、一度だけ内部配列を再配置RemoveArray.Copy、削除すべての要素のための操作を。RemoveAllはるかに効率的です。

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

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


1
これを実行するか、不要な要素へのポインターを2番目のリストに追加してから、ループが終了した後、削除リストを繰り返し、それを使用して要素を削除します。
マイケルディロン

22

ジェネリックリストで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);
        }

2
これは間違いではありませんが、リスト全体を配列にコピーする代わりに、削除するアイテムの2番目の「ストレージ」リストを作成する必要がなくなることを指摘しなければなりません。厳選された要素の2番目のリストは、おそらく項目が少なくなります。
Ahmad Mageed、2009年

20

.ToList()を使用すると、この質問で説明されているように、リストのコピーが 作成されます。ToList()-新しいリストを作成しますか?

ToList()を使用すると、実際にコピーを繰り返し処理するため、元のリストから削除できます。

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

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

     }

1
ただし、パフォーマンスの観点からは、リストをコピーしているため、時間がかかる場合があります。それを行うには簡単な方法ですが、パフォーマンスはそれほど良くありません
Florian K

12

削除するアイテムを決定する関数に副作用がなく、アイテムを変更しない場合(それは純粋な関数です)、シンプルで効率的な(線形時間)ソリューションは次のとおりです。

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イテレータどのように実行できますか?。これはIList<T>、他の多くの回答の2次ランタイムと比較して、の典型的な実装が与えられた場合、線形ランタイムになります。


11

削除はあなたが使用できる条件で行われるので

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

これは、処理によってアイテムが変化せず、副作用がない場合に最適なソリューションです。
CodesInChaos 2014


8

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

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

RemoveAll可能であれば使用してください。それ以外の場合、次のパターンは、リストをインプレースで線形時間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);

3

「パターン」が次のようなものだったらいいのに思います。

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

これは、プログラマの頭の中で進行するプロセスにコードを合わせます。


1
簡単です。ブールフラグの配列を作成し(3ステートタイプを使用しNullable<bool>ます。たとえば、マークなしを許可する場合)、foreachの後にそれを使用してアイテムを削除/保持します。
Dan Bechard、2014

3

述語が要素のブールプロパティであると仮定することにより、それがtrueの場合、要素を削除する必要があります。

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

リストを順番に(逆順ではなく)移動する方が効率的な場合があるため、これに賛成票を入れました。リストが順序付けされているため、削除しない最初のアイテムを見つけたら停止する可能性があります。(i ++がこの例にある「ブレーク」を想像してください。)
FrankKrumnow

3

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

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

リストが非常に大きい場合を除き、これを実行してもパフォーマンスに重大な問題はありません。


3

繰り返しながらリストから項目を削除する最良の方法は、を使用すること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
foreach(var item in list.ToList())

{

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

}

最初のリストからまったく新しいリストを作成するだけです。完全に新しいリストを作成すると、おそらく前の方法よりもパフォーマンスが高くなるため、「正しい」ではなく「簡単」と言います(私はベンチマークに悩まされていません)。通常、このパターンを好みます。 Linq-To-Entitiesの制限。

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

{

item=list[i];

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

}

この方法では、単純な古いForループでリストを逆方向に循環します。コレクションのサイズが変更された場合、これを順方向に行うと問題が発生する可能性がありますが、逆方向は常に安全でなければなりません。


1

反復するリストをコピーします。次に、コピーから削除し、オリジナルを挿入します。逆方向に進むと混乱を招き、並列ループではうまく機能しません。

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

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

1

私はこのようにします

using System.IO;
using System;
using System.Collections.Generic;

class Author
    {
        public string Firstname;
        public string Lastname;
        public int no;
    }

class Program
{
    private static bool isEven(int i) 
    { 
        return ((i % 2) == 0); 
    } 

    static void Main()
    {    
        var authorsList = new List<Author>()
        {
            new Author{ Firstname = "Bob", Lastname = "Smith", no = 2 },
            new Author{ Firstname = "Fred", Lastname = "Jones", no = 3 },
            new Author{ Firstname = "Brian", Lastname = "Brains", no = 4 },
            new Author{ Firstname = "Billy", Lastname = "TheKid", no = 1 }
        };

        authorsList.RemoveAll(item => isEven(item.no));

        foreach(var auth in authorsList)
        {
            Console.WriteLine(auth.Firstname + " " + auth.Lastname);
        }
    }
}

出力

Fred Jones
Billy TheKid

0

同じような状況で、特定のの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

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

より高速なアプローチは、リストをスキャンして、削除する最初のアイテム(ある場合)を見つけ、その時点から、保持する必要がある各アイテムを、それが属する場所にコピーすることです。これが完了すると、Rアイテムを保持する必要がある場合、リストの最初のRアイテムがそれらのRアイテムになり、削除が必要なすべてのアイテムが最後になります。それらのアイテムが逆の順序で削除された場合、システムはそれらのいずれかをコピーする必要がなくなるため、リストにN個のアイテムがあり、そのうち最初のFのすべてを含むR個のアイテムが保持されている場合は、 RFアイテムをコピーし、リストを1アイテムNR回縮小します。すべての線形時間。


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

C#での簡単な方法の1つは、削除するものにマークを付け、新しいリストを作成して繰り返す...

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

またはさらに簡単にlinqを使用します。

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

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


0

プロパティを使用して削除する要素をトレースし、処理後にすべて削除します。

using System.Linq;

List<MyProperty> _Group = new List<MyProperty>();
// ... add elements

bool cond = true;
foreach (MyProperty currObj in _Group)
{
    if (cond) 
    {
        // SET - element can be deleted
        currObj.REMOVE_ME = true;
    }
}
// RESET
_Group.RemoveAll(r => r.REMOVE_ME);

0

これが誰かに役立つ場合に備えて、これに2セントを追加したかっただけです。同様の問題がありましたが、繰り返し処理中に配列リストから複数の要素を削除する必要がありました。エラーに遭遇し、いくつかのインスタンスで複数の要素が削除されていたが、ループのインデックスが維持されなかったために、インデックスが配列リストのサイズよりも大きいことに気づくまで、最も高い賛成投票の回答がほとんどの場合それを行いましたそれを追跡します。私はこれを簡単なチェックで修正しました:

ArrayList place_holder = new ArrayList();
place_holder.Add("1");
place_holder.Add("2");
place_holder.Add("3");
place_holder.Add("4");

for(int i = place_holder.Count-1; i>= 0; i--){
    if(i>= place_holder.Count){
        i = place_holder.Count-1; 
    }

// some method that removes multiple elements here
}

-4
myList.RemoveAt(i--);

simples;

1
simples;ここで何をしますか?
デニー

6
そのデリゲート..それはそれが実行するたびに私の回答を反対票を投じる
SW

SW ROFL!お奨めあなたのコメントが大好きです。
エリックブラウン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.