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