LINQ: RemoveAll и удалить элементы

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

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

var subList = list.Where(x => x.Condition);
list.RemoveAll(x => x.Condition);

или

var subList = list.Where(x => x.Condition);
list.RemoveAll(x => subList.Contains(x));

Является ли это одним из лучших способов? Если да, то какой? Если это не так, как мне это сделать?

Ответ 1

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

var sublist = list.Where(x => x.Condition).ToArray();
list.RemoveAll(x => x.Condition);

Второй пример - O (n ^ 2) без всякой причины, а последнее отлично, но менее читаемо.

Изменить: теперь, когда я перечитываю ваш последний пример, обратите внимание, что, поскольку он написан прямо сейчас, он будет вынимать каждый другой элемент. Вам не нужно проверять состояние, и строка удаления должна быть list.RemoveAt(i--);, потому что после удаления элемент i+1 th становится элементом i th, а когда вы увеличиваете i, вы пропускаете его.

Ответ 2

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

ILookup<bool, Customer> lookup = list.ToLookup(x => x.Condition);
List<Customer> sublist = lookup[true].ToList();
list = lookup[false].ToList();

Или, если вам нужно изменить исходный экземпляр...

list.Clear();
list.AddRange(lookup[false]);

Ответ 3

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

Я сделал следующие методы расширения:

public static class ListExtensions
{
    /// <summary>
    /// Modifies the list by removing all items that match the predicate. Outputs the removed items.
    /// </summary>
    public static void RemoveWhere<T>(this List<T> input, Predicate<T> predicate, out List<T> removedItems)
    {
        removedItems = input.Where(item => predicate(item)).ToList();
        input.RemoveAll(predicate);
    }

    /// <summary>
    /// Modifies the list by removing all items that match the predicate. Calls the given action for each removed item.
    /// </summary>
    public static void RemoveWhere<T>(this List<T> input, Predicate<T> predicate, Action<T> actionOnRemovedItem)
    {
        RemoveWhere(input, predicate, out var removedItems);
        foreach (var removedItem in removedItems) actionOnRemovedItem(removedItem);
    }
}

Пример использования:

items.RemoveWhere(item => item.IsWrong, removedItem =>
    errorLog.AppendLine($"{removedItem} was wrong."));

Ответ 4

Первый вариант хорош, но он делает два запуска по сбору. Вы можете сделать это за один прогон, выполнив дополнительную логику внутри предиката:

        var removedItems = new List<Example>();
        list.RemoveAll(x =>
        {
            if (x.Condition)
            {
                removedItems.Add(x);
                return true;
            }

            return false;
        });

Вы также можете заключить его в расширение для удобства:

public static class ListExtensions
{
    public static int RemoveAll<T>(this List<T> list, Predicate<T> predicate, Action<T> action)
    {
        return list.RemoveAll(item =>
        {
            if (predicate(item))
            {
                action(item);
                return true;
            }

            return false;
        });
    }
}

И используйте вот так:

        var removedItems = new List<Example>();
        list.RemoveAll(x => x.Condition, x => removedItems.Add(x));