Список <object>.RemoveAll - Как создать соответствующий предикат - PullRequest
40 голосов
/ 18 июня 2010

Это небольшой вопрос - я все еще довольно новичок в C # и дженериках и совершенно не знаком с предикатами, делегатами и лямбда-выражениями ...

У меня есть класс 'Inquiries', который содержитобщий список другого класса под названием «Транспортные средства».Я создаю код для добавления / редактирования / удаления транспортных средств из родительского запроса.И в данный момент я специально смотрю на удаления.

Из того, что я прочитал до сих пор, кажется, что я могу использовать Vehicles.RemoveAll (), чтобы удалить элемент с определенным идентификатором VehicleID или все элементы.с определенным EnquiryID.Моя проблема заключается в понимании того, как кормить. Удалите все правильные предикаты - примеры, которые я видел, слишком упрощены (или, возможно, я слишком упрощен, учитывая отсутствие у меня знаний о предикатах, делегатах и ​​лямбда-выражениях).

Так что еслиУ меня было List<Of Vehicle> Vehicles, где у каждого транспортного средства было EnquiryID, как бы я использовал Vehicles.RemoveAll(), чтобы удалить все транспортные средства для данного идентификатора EnquiryID?

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

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

Ответы [ 5 ]

84 голосов
/ 18 июня 2010

Методы RemoveAll() принимают делегата Predicate<T> (пока здесь ничего нового).Предикат указывает на метод, который просто возвращает истину или ложь.Конечно, RemoveAll удалит из коллекции все экземпляры T, которые возвращают True с примененным предикатом.

C # 3.0 позволяет разработчику использовать несколько методов для передачи предиката в метод RemoveAll(и не только этот ...).Вы можете использовать:

Лямбда-выражения

vehicles.RemoveAll(vehicle => vehicle.EnquiryID == 123);

Анонимные методы

vehicles.RemoveAll(delegate(Vehicle v) {
  return v.EnquiryID == 1;
});

Нормальные методы

vehicles.RemoveAll(VehicleCustomPredicate);
private static bool
VehicleCustomPredicate (Vehicle v) {
    return v.EnquiryID == 1; 
}
15 голосов
/ 18 июня 2010

Предикат в T - это делегат, который принимает T и возвращает bool.List .RemoveAll удалит все элементы в списке, где вызов предиката возвращает true.Самый простой способ предоставить простой предикат - это обычно лямбда-выражение , но вы также можете использовать анонимные методы или реальные методы.

{
    List<Vehicle> vehicles;
    // Using a lambda
    vehicles.RemoveAll(vehicle => vehicle.EnquiryID == 123);
    // Using an equivalent anonymous method
    vehicles.RemoveAll(delegate(Vehicle vehicle)
    {
        return vehicle.EnquiryID == 123;
    });
    // Using an equivalent actual method
    vehicles.RemoveAll(VehiclePredicate);
}

private static bool VehiclePredicate(Vehicle vehicle)
{
    return vehicle.EnquiryID == 123;
}
6 голосов
/ 18 июня 2010

Это должно сработать (где enquiryId - это идентификатор, с которым вам нужно сопоставить):

vehicles.RemoveAll(vehicle => vehicle.EnquiryID == enquiryId);

Это означает, что каждое транспортное средство в списке передается в лямбда-предикат, оценивая предикат.Если предикат возвращает истину (т. Е. vehicle.EnquiryID == enquiryId), то текущее транспортное средство будет удалено из списка.

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

1 голос
/ 03 декабря 2015

Я хотел ответить на вопрос, которого пока нет ни у одного из ответов:

Из того, что я прочитал до сих пор, кажется, что я могу использовать Vehicles.RemoveAll (), чтобы удалить элемент сконкретный идентификатор транспортного средства.В качестве дополнительного вопроса, является ли общий список лучшим репозиторием для этих объектов?

Если предположить, что VehicleID уникален, как следует из названия, список является ужасно неэффективным способом их хранения, когдаВы получаете много транспортных средств, так как удаление (и другие методы, такие как Find) все еще O (n).Взгляните на HashSet<Vehicle> вместо этого, он имеет удаление O (1) (и другие методы), используя:

int GetHashCode(Vehicle vehicle){return vehicle.VehicleID;}
int Equals(Vehicle v1, Vehicle v2){return v1.VehicleID == v2.VehicleID;}

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

В этом случае лучшей альтернативой является создание Dictionary<int, List<Vehicle>>, который сопоставляет EnquiryIDs с транспортными средствами и сохраняет его додата при добавлении / удалении транспортных средств.Удаление этих транспортных средств из HashSet - это операция O (m), где m - количество транспортных средств с определенным идентификатором EnquiryID.

1 голос
/ 18 июля 2014

Немного не по теме, но скажу, что я хочу удалить все 2 с из списка. Вот очень элегантный способ сделать это.

void RemoveAll<T>(T item,List<T> list)
{
    while(list.Contains(item)) list.Remove(item);
}

с предикатом:

void RemoveAll<T>(Func<T,bool> predicate,List<T> list)
{
    while(list.Any(predicate)) list.Remove(list.First(predicate));
}

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

Хотя ваши примеры короткие и компактные, ни один не является элегантным с точки зрения эффективности; первое плохо при O (n 2 ), второе абсолютно бездарно при O (n 3 ). Алгоритмическая эффективность O (n 2 ) является плохой, и ее следует по возможности избегать, особенно в коде общего назначения; эффективность O (n 3 ) ужасна и ее следует избегать во всех случаях, кроме случаев, когда вы знаете, что n всегда будет очень маленьким. Некоторые могут отказаться от своей «преждевременной оптимизации - корня всех злых» боевых топоров, но они делают это наивно, потому что они не совсем понимают последствия квадратичного роста, поскольку никогда не кодировали алгоритмы, которые должны обрабатывать большие наборы данных. В результате их алгоритмы обработки небольших наборов данных работают, как правило, медленнее, чем могли бы, и они даже не подозревают, что могли бы работать быстрее. Разница между эффективным алгоритмом и неэффективным алгоритмом часто неуловима, но разница в производительности может быть существенной. Ключом к пониманию производительности вашего алгоритма является понимание характеристик производительности примитивов, которые вы выбираете для использования.

В вашем первом примере list.Contains() и Remove() - оба O (n), поэтому цикл while() с одним в предикате и другим в теле - O (n 2 ); ну, технически O (m * n), но оно приближается к O (n 2 ), так как количество удаляемых элементов (m) приближается к длине списка (n).

Ваш второй пример еще хуже: O (n 3 ), потому что каждый раз, когда вы звоните Remove(), вы также звоните First(predicate), что также O (n). Подумайте об этом: Any(predicate) проходит по списку , ища любой элемент, для которого predicate() возвращает true. Как только он находит первый такой элемент, он возвращает истину. В теле цикла while() вы затем вызываете list.First(predicate), который повторяет по списку второй раз , ища тот же элемент, который уже был найден list.Any(predicate). Как только First() найдет его, он возвращает тот элемент, который передается в list.Remove(), который проходит по списку в третий раз , чтобы еще раз найти тот же элемент, который был ранее найден Any() и First(), чтобы окончательно удалить его. После удаления весь процесс начинается сначала с немного более короткого списка , повторяя все циклы снова и снова и снова, начиная с начала каждый раз , пока, наконец, не останется больше элементов соответствующие предикаты остаются. Таким образом, производительность вашего второго примера равна O (m * m * n) или O (n 3 ), когда m приближается к n.

Лучшим вариантом для удаления всех элементов из списка, которые соответствуют некоторому предикату, является использование собственного метода List<T>.RemoveAll(predicate) общего списка, который равен O (n), если ваш предикат равен O (1). Техника цикла for(), которая проходит по списку только один раз, вызывая list.RemoveAt() для каждого удаляемого элемента, может казаться равной O (n), поскольку кажется, что она проходит через цикл только один раз. Такое решение является более эффективным, чем ваш первый пример, но только на постоянный коэффициент, который с точки зрения алгоритмической эффективности незначителен. Даже реализация цикла for() - это O (m * n), поскольку каждый вызов Remove() - это O (n). Поскольку сам цикл for() равен O (n), и он вызывает Remove() m раз, рост цикла for() равен O (n 2 ), когда m приближается к n.

...