Эффективное удаление элемента из 'foreach'

на данный момент лучшее, что я мог придумать, это:

bool oneMoreTime = true;
while (oneMoreTime)
{
    ItemType toDelete=null;
    oneMoreTime=false;
    foreach (ItemType item in collection)
    {
        if (ShouldBeDeleted(item))
        {
            toDelete=item;
            break;
        }
    }
    if (toDelete!=null)
    {
        collection.Remove(toDelete);
        oneMoreTime=true;
    }
}

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

6 ответов


метод" RemoveAll " является лучшим.

Другой распространенный метод:

var itemsToBeDeleted = collection.Where(i=>ShouldBeDeleted(i)).ToList();
foreach(var itemToBeDeleted in itemsToBeDeleted)
    collection.Remove(itemToBeDeleted);

Другой распространенный метод - использовать цикл" for", но убедитесь, что вы идете назад:

for (int i = collection.Count - 1; i >= 0; --i)
    if (ShouldBeDeleted(collection[i]))
        collection.RemoveAt(i);

Другой распространенный метод заключается в добавлении элементов, которые не удаляется в новую коллекцию:

var newCollection = new List<whatever>();
foreach(var item in collection.Where(i=>!ShouldBeDeleted(i))
    newCollection.Add(item);

и теперь у вас есть две коллекции. Метод, который мне особенно нравится, если вы хотите получить две коллекции, - использовать неизменяемые структуры данных. С неизменяемой структурой данных "удаление" элемента не изменяет структуру данных; он возвращает вам новую структуру данных (которая повторно использует биты из старой, если это возможно), у которой нет элемента, который вы удалили. С неизменяемыми структурами данных вы не изменяете то, что вы повторяете, поэтому нет проблем:

var newCollection = oldCollection;
foreach(var item in oldCollection.Where(i=>ShouldBeDeleted(i))
    newCollection = newCollection.Remove(item);

или

var newCollection = ImmutableCollection<whatever>.Empty;
foreach(var item in oldCollection.Where(i=>!ShouldBeDeleted(i))
    newCollection = newCollection.Add(item);

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


Как только я закончил печатать, я вспомнила, что есть лямбда-способ сделать это.

collection.RemoveAll(i=>ShouldBeDeleted(i));

лучше?


вы не можете удалить из коллекции внутри foreach цикл (если это не очень специальная коллекция, имеющая специальный перечислитель). Коллекции BCL будут выдавать исключения, если коллекция изменяется во время перечисления.

вы могли бы использовать for цикл для удаления отдельных элементов и соответствующей настройки индекса. Однако это может быть ошибкой. В зависимости от реализации базовой коллекции также может быть дорогостоящим удаление отдельных элементы. Например, удаление первого элемента List<T> будет копировать все элементы remaning в списке.

лучшим решением часто является создание новой коллекции на основе старой:

var newCollection = collection.Where(item => !ShouldBeDeleted(item)).ToList();

использовать ToList() или ToArray() чтобы создать новую коллекцию или инициализировать конкретный тип коллекции из IEnumerable возвращено Where() предложения.


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

for (int i = collection.Count-1; i >= 0; i--)
{
    if(ShouldBeDeleted(collection[i])
        collection.RemoveAt(i);
}

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


прямая вариация на обратную for петли:

for (int i = 0; i < collection.Count; )
    if (ShouldBeDeleted(collection[i]))
        collection.RemoveAt(i)
    else
        i++;

просто используйте два списка,

первый список-ваш исходный список и Второй список предметов, которые не должны быть удалены.

var filteredItems = new List<ItemType>();

foreach(var item in collection){
    if(!ShouldBeDeleted(item))
        filteredItems.Add(item);
}