Szukam lepszego wzorca do pracy z listą elementów, które każdy musi przetworzyć, a następnie w zależności od wyniku zostaną usunięte z listy.
Nie możesz używać .Remove(element)
wewnątrz foreach (var element in X)
(ponieważ powoduje to Collection was modified; enumeration operation may not execute.
wyjątek) ... nie możesz także używać for (int i = 0; i < elements.Count(); i++)
i .RemoveAt(i)
ponieważ zaburza twoją obecną pozycję w kolekcji względem i
.
Czy jest na to elegancki sposób?
list.RemoveAll(Function(item) item.Value = somevalue)
.size()
zamiast.Count
i.remove(i)
zamiast.removeAt(i)
. Sprytne - dzięki!RemoveAll()
zajmuje to trzy razy więcej czasu niżfor
pętla wsteczna . Zdecydowanie trzymam się pętli, przynajmniej w sekcjach, w których ma to znaczenie.Proste i proste rozwiązanie:
Użyj standardowej pętli for działającej wstecz w swojej kolekcji i
RemoveAt(i)
do usuwania elementów.źródło
Odwrotna iteracja powinna być pierwszą rzeczą, która przychodzi na myśl, gdy chcesz usunąć elementy z kolekcji podczas iteracji nad nią.
Na szczęście istnieje bardziej eleganckie rozwiązanie niż pisanie pętli for, która wymaga niepotrzebnego pisania i może być podatna na błędy.
źródło
Reverse<T>()
tworzy iterator, który przewija listę do tyłu, ale przydziela dla niej dodatkowy bufor o takim samym rozmiarze jak sama lista ( referenceource.microsoft.com/#System.Core/System/Linq/… ).Reverse<T>
nie tylko przegląda oryginalną listę w odwrotnej kolejności (bez przydzielania dodatkowej pamięci). Dlatego obieToList()
iReverse()
mieć taką samą zużycie pamięci (zarówno tworzenie kopii), aleToList()
ma coś do danych nie. DziękiReverse<int>()
, to zastanawiam się, dlaczego jest lista odwrócony, z jakiego powodu.Reverse<T>()
tworzy nowy bufor, nie jestem pewien, czy rozumiem, dlaczego jest to konieczne. Wydaje mi się, że w zależności od podstawowej strukturyEnumerable
, przynajmniej w niektórych przypadkach powinno być możliwe uzyskanie odwrotnej iteracji bez alokacji pamięci liniowej.Jeśli dodasz „.ToList ()” do swojej listy (lub wyników zapytania LINQ), możesz usunąć „element” bezpośrednio z „listy” bez obaw: „ Kolekcja została zmodyfikowana; operacja wyliczenia może nie zostać wykonana ”. błąd. Kompilator tworzy kopię „listy”, abyś mógł bezpiecznie usunąć tablicę.
Chociaż ten wzór nie jest super wydajny, ma naturalny charakter i jest wystarczająco elastyczny dla prawie każdej sytuacji . Na przykład, gdy chcesz zapisać każdy „element” w DB i usunąć go z listy tylko wtedy, gdy zapis DB się powiedzie.
źródło
Wybierz elementy, które chcesz, zamiast próbować usunąć elementy, których nie chcesz. Jest to o wiele łatwiejsze (i ogólnie także bardziej wydajne) niż usuwanie elementów.
Chciałem opublikować to jako komentarz w odpowiedzi na komentarz pozostawiony przez Michaela Dillona poniżej, ale i tak jest on zbyt długi i prawdopodobnie przydatny w mojej odpowiedzi:
Osobiście nigdy nie usuwałbym elementów jeden po drugim, jeśli potrzebujesz usunięcia, to wywołanie,
RemoveAll
które bierze predykat i tylko raz przestawia wewnętrzną tablicę, podczas gdyRemove
wykonujeArray.Copy
operację dla każdego usuwanego elementu.RemoveAll
jest znacznie wydajniejszy.A gdy iterujesz wstecz po liście, masz już indeks elementu, który chcesz usunąć, więc wywołanie byłoby znacznie bardziej wydajne
RemoveAt
, ponieważRemove
najpierw przegląda listę, aby znaleźć indeks elementu, który próbuję usunąć, ale znasz już ten indeks.Podsumowując, nie widzę powodu, aby kiedykolwiek wywoływać
Remove
pętlę for. I najlepiej, jeśli jest to w ogóle możliwe, użyj powyższego kodu, aby przesyłać strumieniowo elementy z listy w razie potrzeby, aby w ogóle nie trzeba było tworzyć drugiej struktury danych.źródło
Użycie ToArray () na ogólnej liście pozwala na usunięcie (pozycji) z ogólnej listy:
źródło
Użycie .ToList () utworzy kopię listy, jak wyjaśniono w tym pytaniu: ToList () - Czy tworzy nową listę?
Używając ToList (), możesz usunąć z oryginalnej listy, ponieważ tak naprawdę iterujesz nad kopią.
źródło
Jeśli funkcja, która określa, które elementy należy usunąć, nie ma skutków ubocznych i nie powoduje mutacji elementu (jest to czysta funkcja), prostym i wydajnym (liniowym czasem) rozwiązaniem jest:
Jeśli są jakieś skutki uboczne, użyłbym czegoś takiego:
Jest to nadal czas liniowy, przy założeniu, że skrót jest dobry. Ale ma zwiększone wykorzystanie pamięci z powodu skrótu.
Wreszcie, jeśli twoja lista jest tylko
IList<T>
zamiastList<T>
, proponuję moją odpowiedź na Jak mogę zrobić ten specjalny iterator Foreach? . Będzie to miało liniowy czas działania przy typowych implementacjachIList<T>
, w porównaniu z kwadratowym czasem działania wielu innych odpowiedzi.źródło
Jak każde usunięcie jest podejmowane pod warunkiem, którego możesz użyć
źródło
źródło
Nie możesz używać foreach, ale możesz iterować do przodu i zarządzać zmienną indeksu pętli po usunięciu elementu, na przykład:
Zauważ, że ogólnie wszystkie te techniki opierają się na zachowaniu iterowanej kolekcji. Pokazana tutaj technika będzie działać ze standardową Listą (T). (Całkiem możliwe jest napisanie własnej klasy kolekcji i iteratora, która umożliwia usuwanie elementów podczas pętli foreach.)
źródło
Użycie
Remove
lubRemoveAt
na liście podczas iteracji po tej liście było celowo utrudnione, ponieważ prawie zawsze jest to niewłaściwe działanie . Możesz być w stanie sprawić, że zadziała z jakąś sprytną sztuczką, ale byłoby to bardzo wolne. Za każdym razem,Remove
gdy dzwonisz , musi przejrzeć całą listę, aby znaleźć element, który chcesz usunąć. Za każdym razem,RemoveAt
gdy dzwonisz , musi przesuwać kolejne elementy o 1 pozycję w lewo. Jako takie, każde rozwiązanie wykorzystująceRemove
lubRemoveAt
wymagałoby czasu kwadratowego, O (n²) .Użyj,
RemoveAll
jeśli możesz. W przeciwnym razie następujący wzór będzie filtrował listę w miejscu w czasie liniowym, O (n) .źródło
Chciałbym, żeby „wzór” był mniej więcej taki:
To zrównuje kod z procesem zachodzącym w mózgu programisty.
źródło
Nullable<bool>
Jeśli chcesz zezwolić na nieoznaczone), a następnie użyj go po foreach, aby usunąć / zachować elementy.Zakładając, że predykat jest logiczną właściwością elementu, że jeśli jest to prawda, element należy usunąć:
źródło
Ponownie przypisałbym listę z zapytania LINQ, które odfiltrowało elementy, których nie chciałeś zachować.
Jeśli lista nie jest bardzo duża, nie powinno to powodować znaczących problemów z wydajnością.
źródło
Najlepszym sposobem na usunięcie elementów z listy podczas iteracji jest użycie
RemoveAll()
. Ale główną troską napisaną przez ludzi jest to, że muszą robić pewne złożone rzeczy w pętli i / lub mieć złożone przypadki porównywania.Rozwiązaniem jest nadal używać,
RemoveAll()
ale używaj tej notacji:źródło
Po prostu utwórz całkowicie nową listę od pierwszego. Mówię „Łatwy” zamiast „Właściwy”, ponieważ tworzenie zupełnie nowej listy prawdopodobnie wiąże się z wyższą wydajnością w porównaniu z poprzednią metodą (nie zawracałem sobie głowy żadnym testem porównawczym.) Ogólnie wolę ten wzorzec, może być również przydatny w pokonywaniu Ograniczenia Linq-To-Entities.
W ten sposób lista jest przewijana do tyłu za pomocą zwykłej starej pętli For. Wykonanie tego do przodu może być problematyczne, jeśli zmieni się rozmiar kolekcji, ale wstecz zawsze powinno być bezpieczne.
źródło
Skopiuj listę, którą iterujesz. Następnie usuń z kopii i połącz oryginał. Cofanie się jest mylące i nie działa dobrze przy równoległym zapętlaniu.
źródło
Zrobiłbym tak
WYNIK
źródło
Znalazłem się w podobnej sytuacji, w której musiałem usunąć każdy n- ty element danego elementu
List<T>
.źródło
Koszt usunięcia elementu z listy jest proporcjonalny do liczby elementów następujących po elemencie do usunięcia. W przypadku, gdy pierwsza połowa elementów kwalifikuje się do usunięcia, każde podejście polegające na indywidualnym usunięciu elementów będzie musiało wykonać około N * N / 4 operacji kopiowania elementów, co może być bardzo kosztowne, jeśli lista jest duża .
Szybszym rozwiązaniem jest przejrzenie listy w celu znalezienia pierwszego elementu do usunięcia (jeśli istnieje), a następnie skopiowanie każdego elementu, który powinien zostać zachowany, do miejsca, do którego należy. Po wykonaniu tej czynności, jeśli R elementów powinno zostać zachowanych, pierwszymi R elementami na liście będą te R elementów, a wszystkie elementy wymagające usunięcia znajdą się na końcu. Jeśli te elementy zostaną usunięte w odwrotnej kolejności, system nie będzie musiał kopiować żadnego z nich, więc jeśli lista zawiera N elementów, z których R, w tym wszystkie pierwsze F, zostały zachowane, konieczne będzie skopiuj elementy RF i zmniejsz listę o jeden NR NR pozycji. Cały czas liniowy.
źródło
Moje podejście polega na tym, że najpierw tworzę listę indeksów, które należy usunąć. Następnie przeglądam indeksy i usuwam elementy z początkowej listy. Wygląda to tak:
źródło
W C # jednym prostym sposobem jest zaznaczenie tych, które chcesz usunąć, a następnie utworzenie nowej listy, aby iterować ...
lub jeszcze prostsze użycie linq ....
ale warto zastanowić się, czy inne zadania lub wątki będą miały dostęp do tej samej listy w tym samym czasie, gdy jesteś zajęty usuwaniem, i może zamiast tego użyj ConcurrentList.
źródło
Śledź elementy do usunięcia za pomocą właściwości i usuń je wszystkie po procesie.
źródło
Chciałem tylko dodać do tego moje 2 centy, na wypadek gdyby komuś to pomogło, miałem podobny problem, ale musiałem usunąć wiele elementów z listy tablic podczas iteracji. najwyżej oceniona odpowiedź zrobiła to dla mnie w przeważającej części, dopóki nie natknąłem się na błędy i nie zdałem sobie sprawy, że indeks był większy niż rozmiar listy tablic w niektórych przypadkach, ponieważ usuwano wiele elementów, ale indeks pętli nie zachowywał się ślad tego. Naprawiłem to prostym sprawdzeniem:
źródło
źródło
simples;
tu robi?