2009-10-17 2 views
325

Я ищу лучше шаблон для работы со списком элементов, которые каждый из них обрабатывается, а затем в зависимости от результата удаляются из списка.Как удалить элементы из общего списка, итерации по нему?

Вы не можете использовать .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.

Есть ли элегантный способ сделать это?

ответ

539

итерацию списка в обратном порядке с циклом:

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 method с предикатом для проверки против:

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

Для тех, кто приходит с Java, список C# похож на ArrayList, поскольку вставка/удаление - это O (n), а поиск по индексу - O (1). Это * не * традиционный связанный список. Кажется, немного неудачный C# использует слово «Список» для описания этой структуры данных, так как это напоминает классический связанный список. –

+51

Ничто в названии 'List' не говорит 'LinkedList'. Люди, пришедшие с других языков, кроме Java, могут быть сбиты с толку, когда это будет связанный список. – GolezTrol

+3

Я оказался здесь через поиск vb.net, поэтому, если кто-то хочет эквивалентный синтаксис vb.net для RemoveAll: 'list.RemoveAll (Function (item) item.Value = somevalue)' – pseudocoder

2

Я бы переназначил список из запроса LINQ, который отфильтровал элементы, которые вы не хотели сохранять.

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

Если список не очень велик, при выполнении этого не должно быть серьезных проблем с производительностью.

69

Простое и простое решение:

Используйте стандартный для цикла работает обратной на вашей коллекции и RemoveAt(i) для удаления элементов.

+7

+1: назад обход является ключом :) –

+0

это ЭФФЕКТИВНОЕ и отлично работает. –

18

Использование ToArray() на общий список позволяет сделать Remove (пункт) на ваш общий список:

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

Это не так, но я должен указать, что это обходит необходимость создания второго «хранилища» списка элементов, которые вы хотите удалить, за счет копирования всего списка в массив. Второй список элементов, выбранных вручную, вероятно, будет содержать меньше предметов. –

18

Выберите элементы сделать хотите вместо того, чтобы удалить элементы вы не хочет. Это намного проще (и, как правило, более эффективно), чем удаление элементов.

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

Я хотел отправить это как комментарий в ответ на комментарий оставил Майкл Диллон ниже, но это слишком долго, и, вероятно, полезно иметь в своем ответе так или иначе:

Лично я бы никогда удалите элементы по одному, если вам нужно удалить, затем вызовите RemoveAll, который берет предикат и только один раз перестраивает внутренний массив, тогда как Remove выполняет операцию Array.Copy для каждого элемента, который вы удаляете. RemoveAll значительно эффективнее.

И когда вы назад итерацию по списку, у вас уже есть индекс элемента, который вы хотите удалить, так что было бы гораздо эффективнее назвать RemoveAt, потому что Remove первый делает обход списка, чтобы найти индекс элемента, который вы пытаетесь удалить, но вы уже знаете этот индекс.

В общем, я не вижу причин когда-либо звонить Remove в цикле. И в идеале, если это вообще возможно, используйте приведенный выше код для потоковой передачи элементов из списка по мере необходимости, чтобы вторая структура данных не создавалась вообще.

+1

Либо это, либо добавить указатель на нежелательные элементы во второй список, а затем, после окончания цикла, повторите удаление списка удаления и используйте это для удаления элементов. –

52

Обратное итерационное движение должно быть первым, что приходит на ум, когда вы хотите удалить элементы из коллекции, итерации по ней.

К счастью, есть более элегантное решение, чем писать цикл 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); 
    } 
} 
+1

Как насчет нетривиального примера, например {"apple", "mango"}? – user275801

+5

Это отлично сработало для меня. Простой и элегантный, а также минимальные изменения в моем коде. –

+1

Это просто гений Flippin? И я согласен с @StephenMacDougall, мне не нужно использовать эти C++ 'y для циклов и просто работать с LINQ, как и предполагалось. – ppumkin

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

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

Использование .ToList() сделает копию вашего списка, как описано в этом вопросе: ToList()-- Does it Create a New List?

Используя ToList(), вы можете удалить из исходного списка, потому что вы на самом деле повторяете копию.

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

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

    } 
+1

Но с точки зрения производительности вы копируете свой список, что может занять некоторое время. Хороший и простой способ сделать это, но с не очень хорошей производительностью –

0

я оказался в подобной ситуации, когда я должен был удалить все п я элемента в данных 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 
} 
2

Я желание «Растр» было что-то вроде этого:

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 », если вы хотите разрешить немаркированные), а затем используйте его после foreach для удаления/сохранения элементов. – Dan

2

Предполагая, что предиката является логическим свойством элемента, что, если это правда, то этот элемент должен быть удален:

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

Я дал это upvote, поскольку иногда было бы более эффективно перемещаться по списку в порядке (а не в обратном порядке). Возможно, вы могли бы остановиться при поиске первого элемента, который нельзя удалить, потому что список упорядочен. (Представьте себе «break», где i ++ находится в этом примере. – FrankKrumnow

9

Как любые удалить берутся по условию можно использовать

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

Это лучшее решение, если обработка не мутирует элемент и не имеет побочных эффектов. – CodesInChaos

9

Если функция, которая определяет, какие элементы для удаления не имеет побочных эффектов и не мутируют элемент (это чистая функция), простой и эффективный (линейное время) решение:

list.RemoveAll(condition); 

Если есть побочные эффекты, я хотел бы использовать что-то вроде:

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

Это еще линейное время, предполагая, что хэш хорошо. Но из-за hashset у него увеличенное использование памяти.

И наконец, если ваш список является только IList<T> вместо List<T> Предлагаю свой ответ How can I do this special foreach iterator?.Это будет иметь линейное время выполнения, заданное типичными реализациями IList<T>, по сравнению с квадратичным временем выполнения многих других ответов.

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

Если вы добавить «.ToList()» в список (или результаты запроса LINQ), вы можете удалить „пункт“ непосредственно из „списка“ без страшной "Коллекции был изменен , операция перечисления может не выполняться. " ошибка. Компилятор делает копию «списка», чтобы вы могли безопасно выполнить удаление в массиве.

Хотя эта модель не супер эффективный, он имеет естественный вид и достаточно гибкой практически для любой ситуации. Например, когда вы хотите сохранить каждый «элемент» в БД и удалить его из списка только тогда, когда удастся сохранить БД.

+3

Это лучшее решение, если эффективность не имеет решающего значения. – IvanP

+1

это быстрее и более читабельно: list.RemoveAll (i => true); –

5

Вы не можете использовать Еогеасп, но вы можете перебирать вперед и управлять индексным переменным цикл при удалении элемента, например, так:

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

Обратите внимание, что в целом все эти методы опираются на поведении из повторяющейся коллекции. Представленная здесь техника будет работать со стандартным списком (T). (Вполне возможно, чтобы написать свой собственный класс коллекции и итератора, что делает позволяет удаление элемента во время цикла Еогеасп.)

-4
myList.RemoveAt(i--); 

simples; 
+1

Что делает 'simples;' здесь? – Denny

+0

его делегат .. он уменьшает мой ответ каждый раз, когда он запускается –

0

Стоимость удаления элемента из списка, пропорционально числу следующих пунктов тот, который нужно удалить. В случае, когда первая половина предметов подходит для удаления, любой подход, основанный на удалении элементов индивидуально, в конечном итоге должен будет выполнить операции N-N операций с копиями N * N/4, которые могут стать очень дорогими, если список большой ,

Более быстрый подход состоит в том, чтобы просмотреть список, чтобы найти первый элемент, который необходимо удалить (если есть), а затем с этой точки переслать каждый элемент, который должен быть сохранен до того места, где он принадлежит. Как только это будет сделано, если элементы R должны быть сохранены, первыми R-элементами в списке будут те R-элементы, и все элементы, требующие удаления, будут в конце. Если эти элементы будут удалены в обратном порядке, системе не придется копировать ни один из них, поэтому, если в списке было N элементов, из которых были сохранены элементы R, включая все первые F, , это необходимо для копирования RF-элементов и сокращения списка по одному пункту NR. Все линейное время.

3

Использование Remove или RemoveAt в списке в то время как итерация этот список намеренно было сделано трудно, потому что это почти всегда неправильно, что нужно сделать. Вы могли бы заставить его работать с каким-то умным трюком, но это было бы очень медленно. Каждый раз, когда вы вызываете Remove, он должен просмотреть весь список, чтобы найти элемент, который вы хотите удалить. Каждый раз, когда вы вызываете RemoveAt, он должен перемещать последующие элементы 1 позиции влево. Таким образом, любое решение с использованием Remove или RemoveAt потребует квадратичного времени, 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); 
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

Скопируйте список, который вы итерируете. Затем удалите из копии и вставьте оригинал. Переход назад сбивает с толку и не работает при параллельном цикле.

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

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

Лучший способ удаления элементов из списка в то время как итерация это использовать 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; 
}); 
Смежные вопросы