Genel bir listeden öğeleri yinelemek için nasıl kaldırılır?


451

Her işlenmesi gereken öğelerin bir listesi ile çalışmak için daha iyi bir desen arıyorum ve daha sonra sonuca bağlı olarak listeden kaldırılır.

Sen kullanamaz .Remove(element)bir iç foreach (var element in X)(o sonuçlanır çünkü Collection was modified; enumeration operation may not execute.istisna) ... ayrıca kullanamaz for (int i = 0; i < elements.Count(); i++)ve .RemoveAt(i)bunun için toplama göreli mevcut konumunuzu bozar çünkü i.

Bunu yapmanın zarif bir yolu var mı?

Yanıtlar:


734

Listenizi for döngüsüyle tersine çevirin:

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

Misal:

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

Alternatif olarak, aşağıdakileri test etmek için RemoveAll yöntemini bir yüklemle kullanabilirsiniz:

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

Göstermek için basitleştirilmiş bir örnek:

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'dan gelenler için, C # Listesi ArrayList gibidir, çünkü ekleme / kaldırma O (n) ve bir indeks yoluyla alma O (1) 'dir. Bu değil geleneksel bağlantılı liste. Biraz talihsiz görünüyor C # klasik veri listesi akla getiriyor çünkü bu veri yapısını tanımlamak için "Liste" kelimesini kullanır.
Jarrod Smith

79
'List' adında hiçbir şey 'LinkedList' demez. Bağlantılı bir liste olacağı zaman Java dışında başka dillerden gelen kişilerin kafası karışabilir.
GolezTrol

5
Herkes vb.AltAll için vb.net eşdeğer sözdizimi isterse bu yüzden bir vb.net arama yoluyla sona erdi:list.RemoveAll(Function(item) item.Value = somevalue)
pseudocoder

1
Bu, Java'da minimum değişiklikle de çalışır: .size()yerine .Countve .remove(i)yerine .removeAt(i). Zeki - teşekkürler!
Sonbahar Leonard

2
Performans için küçük bir test yaptım RemoveAll()ve geri fordöngüden üç kat daha fazla zaman aldığı ortaya çıktı . Bu yüzden, en azından önemli olduğu bölümlerde, kesinlikle döngüye bağlı kalıyorum.
Crusha K. Rool

84

Basit ve anlaşılır bir çözüm:

Koleksiyonunuzda geriye doğru çalışan ve RemoveAt(i)elemanları kaldırmak için standart bir for-loop kullanın .


2
Bir Öğeleri birer birer kaldırarak olduğunu unutmayın değil listenizi sayıda öğe içeriyorsa verimli. O (n ^ 2) olma potansiyeline sahiptir. İlk iki milyar öğenin silinmesinin gerçekleştiği iki milyar maddeli bir Liste düşünün. Her kaldırma işlemi sonraki tüm öğelerin kopyalanmasına zorlar, böylece her biri milyarlarca kez bir kopya öğeyi kopyalarsınız. Bunun tersi yineleme değil, tek seferde kaldırılmasıdır. RemoveAll, her öğenin doğrusal olması için en fazla bir kez kopyalanmasını sağlar. Her seferinde kaldırma işlemi milyar kat daha yavaş olabilir. O (n) ve O (n ^ 2).
Bruce Dawson

71

Bir Koleksiyondan öğeleri yinelemek için kaldırmak istediğinizde, ilk yineleme tersine çevrilmelidir.

Neyse ki, gereksiz yazmayı içeren ve hataya eğilimli olabilen bir for döngüsü yazmaktan daha zarif bir çözüm var.

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
Bu benim için mükemmel çalıştı. Basit, zarif ve kodumda minimum değişiklikler yapılması gerekiyor.
Stephen MacDougall

1
Bu sadece flippin dehası mı? Ve @StephenMacDougall ile hemfikirim, döngüler için bu C ++ 'y kullanmam gerekmiyor ve sadece amaçlandığı gibi LINQ ile çalışmaya başlamıyorum.
Piotr Kula

5
Basit foreach (int myInt in test.ToList ()) {if (myInt% 2 == 0) {test.Remove (myInt); }} Yine de Reverse için bir kopya ayırmanız gerekiyor ve bu dosya Huh? an - neden Ters.
jahav

11
@jedesah Evet, Reverse<T>()listeden geriye doğru giden bir yineleyici oluşturur, ancak listenin kendisi ile aynı boyutta fazladan tampon ayırır ( referenceource.microsoft.com/#System.Core/System/Linq/… ). Reverse<T>sadece orijinal listeyi ters sırayla gözden geçirmekle kalmaz (fazladan bellek ayırmadan). Bu nedenle hem ToList()ve Reverse()aynı bellek tüketimini (hem kopyasını oluşturmak) var, ama ToList()verilerine şey yapmaz. Bununla birlikte Reverse<int>(), listenin neden tersine çevrildiğini merak ediyorum.
jahav

1
@jahav Ne demek istediğini anlıyorum. Bu uygulamanın Reverse<T>()yeni bir tampon oluşturduğu hayal kırıklığı yaratıyor, bunun neden gerekli olduğunu anladığımdan emin değilim. Bana öyle geliyor ki, altta yatan yapıya bağlı olarak, Enumerableen azından bazı durumlarda doğrusal bellek ayırmadan ters iterasyon elde etmek mümkün olmalıdır.
jedesah

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

Eğer varsa ".ToList ()" add korkulan olmadan listenizdeki (veya LINQ sorgusunun sonuçları), doğrudan 'listesinden' 'öğesini' kaldırabilirsiniz için ' Koleksiyon değiştirildi; numaralama işlemi yürütmek olmayabilir .' hata. Derleyici "list" in bir kopyasını oluşturur, böylece dizideki kaldırmayı güvenle yapabilirsiniz.

İken bu model süper verimli değil, doğal bir his sahiptir ve hemen hemen her durum için yeterince esnek . Her bir "öğeyi" bir DB'ye kaydetmek ve yalnızca DB kaydetme başarılı olduğunda listeden kaldırmak istediğinizde olduğu gibi.


6
Verimlilik çok önemli değilse, bu en iyi çözümdür.
IvanP

2
bu da daha hızlı ve daha okunabilir: list.RemoveAll (i => true);
santiago arizti

1
@Greg Küçük, sizi doğru anladım mı - ToList () derleyicisini eklediğinizde kopyalanan koleksiyondan geçiyor, ancak orijinalden kaldırılıyor mu?
Pyrejkee

Listeniz yinelenen öğeler içeriyorsa ve yalnızca listede daha sonra oluşan öğeyi kaldırmak istiyorsanız, bu yanlış öğeyi kaldırmaz mı?
Tim MB

Bu aynı zamanda bir nesne listesi için de çalışır mı?
Tom el Safadi

23

Eğer elemanları seçip yok doğrusu sen unsurları kaldırmaya çalışırken daha istiyoruz değil istiyorum. Bu, elemanları çıkarmaktan çok daha kolaydır (ve genellikle de daha verimlidir).

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

Aşağıdaki Michael Dillon tarafından bırakılan yoruma yanıt olarak bunu bir yorum olarak göndermek istedim, ancak yine de cevabımda olması çok uzun ve muhtemelen yararlı:

Şahsen, kaldırmaya ihtiyacınız varsa, öğeleri tek tek kaldırmam, kaldırmaya ihtiyacınız varsa, RemoveAllbu durumda bir yüklem alır ve yalnızca iç diziyi bir kez yeniden düzenler, oysa kaldırdığınız her öğe için Removebir Array.Copyişlem yapar . RemoveAllçok daha verimlidir.

Ve bir liste üzerinde geriye doğru yinelendiğinizde, kaldırmak istediğiniz öğenin dizinine zaten sahipsiniz, bu nedenle aramak çok daha verimli olacaktır RemoveAt, çünkü Removeönce öğenin dizinini bulmak için listenin bir geçişini yapar kaldırmaya çalışıyorsunuz, ancak bu dizini zaten biliyorsunuz.

Sonuçta Remove, bir for-loop'u aramak için hiçbir neden göremiyorum . İdeal olarak, eğer mümkünse, listeden öğeleri gerektiği gibi akışa almak için yukarıdaki kodu kullanın, böylece hiçbir ikinci veri yapısının oluşturulması gerekmez.


1
Ya bu, ya da ikinci bir listeye istenmeyen öğelere bir işaretçi ekleyin, sonra döngünüz sona erdikten sonra kaldırma listesini yineleyin ve öğeleri kaldırmak için bunu kullanın.
Michael Dillon

22

Genel listede ToArray () kullanmak, genel Listenizde Kaldır (öğe) yapmanızı sağlar:

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

2
Bu yanlış değil, ancak tüm listeyi bir diziye kopyalama pahasına kaldırılmasını istediğiniz öğelerin 2. "depolama" listesini oluşturma ihtiyacını atladığını belirtmek zorundayım. Elle seçilmiş elemanların 2. listesinde muhtemelen daha az öğe olacaktır.
Ahmad Mageed

20

Bu soruda açıklandığı gibi .ToList () kullanılması, listenizin bir kopyasını oluşturur: ToList () - Yeni Liste Oluşturuyor mu?

ToList () kullanarak orijinal listenizden kaldırabilirsiniz, çünkü aslında bir kopya üzerinde yineliyorsunuzdur.

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

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

     }

1
Ancak performans açısından listenizi kopyalarsınız ve bu biraz zaman alabilir. Bunu yapmanın iyi ve kolay yolu, ama çok iyi bir performansla
Florian K

12

Hangi öğelerin silineceğini belirleyen fonksiyonun herhangi bir yan etkisi yoksa ve öğeyi mutasyona uğratmazsa (saf bir işlevdir), basit ve verimli (doğrusal zaman) bir çözümdür:

list.RemoveAll(condition);

Yan etkiler varsa, şöyle bir şey kullanırdım:

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

Bu hala doğrusal bir zaman, karmanın iyi olduğu varsayılarak. Ancak, hashset nedeniyle artan bellek kullanımı vardır.

Listeniz yalnızca olduğunu Nihayet eğer IList<T>yerine bir List<T>ben benim soruya ancak ben bu özel foreach yineleyici nasıl yapabilirim? . Bu, IList<T>diğer birçok cevabın ikinci dereceden çalışma süresine kıyasla, tipik uygulamaları verilen doğrusal çalışma zamanına sahip olacaktır .


11

Bir koşulda herhangi bir kaldırma işlemi yapıldığından,

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

İşlem öğeyi değiştirmezse ve yan etkisi yoksa en iyi çözüm budur.
CodesInChaos

9
List<T> TheList = new List<T>();

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

8

Foreach kullanamazsınız, ancak bir öğeyi kaldırdığınızda döngü dizini değişkeninizi ileri doğru yineleyebilir ve yönetebilirsiniz:

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

Genel olarak tüm bu tekniklerin yinelenen koleksiyonun davranışına bağlı olduğunu unutmayın. Burada gösterilen teknik standart Liste (T) ile çalışacaktır. (Kendi koleksiyon sınıfı yazmak oldukça mümkündür ve yineleyici o yapar bir foreach döngüsü sırasında öğe çıkarılmasına izin verir.)


6

Bu listeyi yinelemekte Removeveya RemoveAtbir listede kullanmak kasıtlı olarak zorlaşmıştır, çünkü neredeyse her zaman yapılacak yanlış şeydir . Bazı akıllı numaralarla çalıştırabilirsiniz, ancak son derece yavaş olur. Her aradığınızda Removekaldırmak istediğiniz öğeyi bulmak için tüm listeyi taraması gerekir. Her aradığınızda RemoveAt, sonraki elemanları 1 konum sola taşımak zorundadır. Bu nedenle, Removeveya kullanan herhangi bir çözüm RemoveAtikinci dereceden zaman gerektirir, O (n²) .

RemoveAllMümkünse kullanın . Aksi takdirde, aşağıdaki desen listeyi yerinde lineer zamanda (O (n)) filtreleyecektir .

// 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

Ben diliyorum "desen" böyle bir şey oldu:

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

Bu, kodu programcının beyninde devam eden süreçle hizalar.


1
Yeterince kolay. Yalnızca bir boolean bayrak dizisi oluşturun (örneğin Nullable<bool>, işaretlenmesine izin vermek istiyorsanız 3 durumlu bir tür kullanın), sonra öğeleri kaldırmak / saklamak için foreach'ten sonra kullanın.
Dan Bechard

3

Yüklemin bir öğenin bir Boolean özelliği olduğunu varsayarak , doğruysa öğenin kaldırılması gerektiğini varsayarsak :

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

Bazen bir liste verdim çünkü bazen Listede (sırayla değil) sırayla hareket etmek daha verimli olabilir. Listenin sıralı olması nedeniyle kaldırılmaması gereken ilk öğeyi bulurken durabilirsiniz. (İ ++ 'ın bu örnekte olduğu yerde bir' sonu 'düşünün.
FrankKrumnow

3

Tutmak istemediğiniz öğeleri filtreleyen bir LINQ sorgusu listesinden yeniden atamak.

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

Liste çok büyük olmadıkça, bunu yaparken önemli bir performans problemi olmamalıdır.


3

Bir öğeyi tekrarlarken bir listeden kaldırmanın en iyi yolu kullanmaktır RemoveAll() . Ancak insanlar tarafından yazılan temel endişe, döngü içinde bazı karmaşık şeyler yapmak ve / veya karmaşık karşılaştırma vakalarına sahip olmalarıdır.

Çözüm hala RemoveAll()bu gösterimi kullanmak ama kullanmaktır:

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

}

Sadece birinciden tamamen yeni bir liste oluşturun. Tamamen yeni bir liste oluşturmak muhtemelen önceki yönteme göre bir performans primi geldiğinden "Doğru" yerine "Kolay" diyorum (herhangi bir kıyaslama ile uğraşmadım.) Genellikle bu kalıbı tercih ederim, üstesinden gelmede de yararlı olabilir Linq-To-Entities sınırlamaları.

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

{

item=list[i];

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

}

Bu şekilde, eski bir For döngüsü ile listede geriye doğru dolaşır. Koleksiyonun boyutu değişirse bunu ileri doğru yapmak sorunlu olabilir, ancak geriye doğru her zaman güvenli olmalıdır.


1

Yinelemekte olduğunuz listeyi kopyalayın. Sonra kopyadan çıkarın ve orijinali birleştirin. Geriye gitmek kafa karıştırıcıdır ve paralel döngü yaparken iyi çalışmaz.

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

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

1

Bunu yapardım

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

ÇIKTI

Fred Jones
Billy TheKid

0

Ben Her n kaldırmak zorunda nerede benzer bir durumda buldum inci verilen Elemanı 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

Bir öğeyi listeden kaldırmanın maliyeti, kaldırılacak öğeyi izleyen öğelerin sayısıyla orantılıdır. Maddelerin ilk yarısının kaldırmaya hak kazanması durumunda, öğeleri ayrı ayrı kaldırmaya dayanan herhangi bir yaklaşım, N * N / 4 öğe kopyalama işlemleri yapmak zorunda kalır ve bu da liste büyükse çok pahalı olabilir. .

Daha hızlı bir yaklaşım, kaldırılacak ilk öğeyi (varsa) bulmak için listeyi taramak ve daha sonra bu noktadan, ait olması gereken yere tutulması gereken her öğeyi ileriye doğru kopyalamaktır. Bu yapıldıktan sonra, R öğelerinin tutulması gerekiyorsa, listedeki ilk R öğeleri bu R öğeleri olacaktır ve silinmesi gereken tüm öğeler sonunda olacaktır. Bu öğeler ters sırayla silinirse, sistem bunlardan herhangi birini kopyalamak zorunda kalmaz, bu nedenle listede ilk F'nin tümü de dahil olmak üzere R öğelerinin tutulduğu N öğe varsa, RF öğelerini kopyalayın ve listeyi NR kez bir öğe küçültün. Tüm doğrusal zaman.


0

Benim yaklaşımım önce silinmesi gereken bir indeks listesi oluşturmak. Daha sonra indeksler üzerinde döngü ve ilk listeden öğeleri kaldırmak. Bu şuna benzer:

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 # 'da kolay bir yol, silmek istediklerinizi işaretlemek ve ardından tekrarlamak için yeni bir liste oluşturmaktır ...

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

hatta daha basit kullanım linq ....

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

ancak diğer görevlerin veya iş parçacıklarının aynı listeye kaldırmaya aynı anda erişip erişemeyeceğini ve bunun yerine bir ConcurrentList kullanıp kullanamayacağınızı düşünmeye değer.


0

Bir özellik ile kaldırılacak öğeleri izleyin ve işlemden sonra hepsini kaldırın.

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

Bu herkesin yardımcı olması durumunda sadece benim 2 sent eklemek istedim, ben benzer bir sorun vardı ama yinelenen iken bir dizi listesinden birden çok öğe kaldırmak gerekiyordu. hatalarla karşılaşıncaya ve bazı durumlarda dizinin dizi listesinin boyutundan daha büyük olduğunu fark edene kadar en üst düzeydeki yanıt benim için yaptı, çünkü birden çok öğe kaldırıldı, ancak döngünün dizini tutmadı izler. Bunu basit bir kontrolle düzelttim:

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;Burada ne yapar ?
Denny

6
onun bir delege .. her zaman cevap benim cevap aşağı
SW

SW ROFL! Yorumunu sevmelisin.
Erick Brown
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.