Использование Linq для удаления из набора, где ключ существует в другом наборе?
Каков правильный способ выполнения вычитания с помощью Linq? У меня есть список из 8000+ банков, где я хочу удалить часть из них на основе номера маршрутизации. Часть находится в другом списке, а номер маршрутизации - это свойство ключа для обоих. Вот упрощение:
public class Bank
{
public string RoutingNumber { get; set; }
public string Name { get; set; }
}
var removeThese = new List<string>() { "111", "444", "777" };
var banks = new List<Bank>()
{
new Bank() { RoutingNumber = "111", Name = "First Federal" },
new Bank() { RoutingNumber = "222", Name = "Second Federal" },
new Bank() { RoutingNumber = "333", Name = "Third Federal" },
new Bank() { RoutingNumber = "444", Name = "Fourth Federal" },
new Bank() { RoutingNumber = "555", Name = "Fifth Federal" },
new Bank() { RoutingNumber = "666", Name = "Sixth Federal" },
new Bank() { RoutingNumber = "777", Name = "Seventh Federal" },
new Bank() { RoutingNumber = "888", Name = "Eight Federal" },
new Bank() { RoutingNumber = "999", Name = "Ninth Federal" },
};
var query = banks.Remove(banks.Where(x => removeThese.Contains(x.RoutingNumber)));
Ответы
Ответ 1
Это должно сделать трюк:
var toRemove = banks.Where(x => removeThese.Contains(x.RoutingNumber)).ToList();
var query = banks.RemoveAll(x => toRemove.Contains(x));
Первый шаг - убедиться, что вам не нужно повторно запускать этот первый запрос снова и снова, когда изменяется banks
.
Это тоже должно работать:
var query = banks.Except(toRemove);
как вторая строка.
ИЗМЕНИТЬ
Тим Шмельтер указал, что для работы Except
вам необходимо переопределить Equals
и GetHashCode
.
Итак, вы можете реализовать его так:
public override string ToString()
{
... any serialization will do, for instance JSON or CSV or XML ...
... OR any serialization that identifies the object quickly, such as:
return "Bank: " + this.RoutingNumber;
}
public override bool Equals(System.Object obj)
{
return ((obj is Bank) && (this.ToString().Equals(obj.ToString()));
}
public override int GetHashCode()
{
return this.ToString().GetHashCode();
}
Ответ 2
Как правило, это меньше работает, чтобы просто вытащить те, которые вам нужны, а не удалять те, которые вы не делаете.
var query = myList.Where(x => !removeThese.Contains(x.RoutingNumber));
Ответ 3
Фильтрация этого типа обычно выполняется с помощью общих конструкций LINQ:
banks = banks.Where(bank => !removeThese.Contains(bank.RoutingNumber)).ToList();
В этом конкретном случае вы также можете использовать List<T>.RemoveAll
, чтобы выполнить фильтрацию на месте, что будет быстрее:
banks.RemoveAll(bank => removeThese.Contains(bank.RoutingNumber));
Кроме того, по соображениям производительности, если количество номеров маршрутизации для удаления велико, вы должны рассмотреть возможность размещения их в HashSet<string>
.
Ответ 4
Либо используйте методы расширения Linq Where
и ToList
для создания нового списка или используйте List.RemoveAll
, что является более эффективным с тех пор он изменяет исходный список:
banks = banks.Where(x => !removeThese.Contains(x.RoutingNumber)).ToList();
banks.RemoveAll(x => removeThese.Contains(x.RoutingNumber));
Конечно, вам нужно отменить условие, так как первое сохраняет, что Where
уходит, а второе удаляет то, что возвращает предикат в RemoveAll
.
Ответ 5
Пробовали ли вы использовать RemoveAll()
?
var query = banks.RemoveAll(p => removeThese.Contains(p.RoutingNumber));
Это приведет к удалению любых значений из banks
, где соответствующая запись присутствует в removeThese
.
query
будет содержать количество записей, удаленных из списка.
Примечание. Оригинальная переменная banks
будет обновляться непосредственно этим запросом; переназначение не требуется.
Ответ 6
Вы можете использовать RemoveAll()
var removedIndexes = banks.RemoveAll(x => removeThese.Contains(x.RoutingNumber));
или
banks = banks.Where(bank => !removeThese.Contains(bank.RoutingNumber)).ToList();