Подтвердить что ты не робот

Список <объект>.RemoveAll - как создать соответствующий предикат

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

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

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

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

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

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

4b9b3361

Ответ 1

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

С# 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; 
}

Ответ 2

Предикат в T является делегатом, который принимает T и возвращает bool. List <T> .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;
}

Ответ 3

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

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

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

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

Ответ 4

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

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. Когда он найдет первый такой элемент, он вернет 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.

Ответ 5

Гигантский necro, но я хотел обратиться к чему-то, что до сих пор не было ответа:

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

Предполагая, что 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;}

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

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