Удалить элементы из одного списка в другом

206

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

Так скажем, у меня есть это в качестве гипотетического примера

List<car> list1 = GetTheList();
List<car> list2 = GetSomeOtherList();

Я хочу пройти через list1 с помощью foreach и удалить каждый элемент в List1, который также содержится в List2.

Я не совсем уверен, как это сделать, поскольку foreach не основан на индексах.

PositiveGuy
источник
1
Вы хотите удалить элементы в List1, которые также есть в List2?
Шринивас Редди Татиарти,
1
Что должно произойти, если у вас есть list1 = {foo1} и list2 = {foo1, foo1}. Следует ли удалить все копии foo1 из списка list2 или только из первого?
Марк Байерс
2
-1 - Я отклонил каждый ответ в этом вопросе, потому что я думал, что они все были неправы, но кажется, что вопрос просто задан ужасно. Теперь я не могу их изменить - извинения. Вы хотите удалить элементы из list1существующих в list2или вы хотите удалить элементы из list2существующих в list1? Во время этого комментария каждый предоставленный ответ будет выполнять последний.
Джон Раш
7
@ Джон Раш, ты должен быть немного менее доволен этими отрицательными голосами. Некоторые из ответов являются довольно концептуальными и демонстрируют, как достичь того, чего хочет ОП, даже не касаясь списков, упомянутых в вопросе.
Жоау Анджело
3
@ Марк - вы правы, я полностью виноват - вот почему я разместил здесь комментарий, объясняющий, что произошло, я искал предыдущий ответ, который у меня уже был на аналогичный вопрос тем временем после моего голосования, и я собирался уйти комментарии после того, как я нашел это - оказывается, что это не лучший процесс для этого!
Джон Раш

Ответы:

358

Вы можете использовать кроме :

List<car> list1 = GetTheList();
List<car> list2 = GetSomeOtherList();
List<car> result = list2.Except(list1).ToList();

Возможно, вам даже не нужны эти временные переменные:

List<car> result = GetSomeOtherList().Except(GetTheList()).ToList();

Обратите внимание, что Exceptни один из этих списков не изменяется - он создает новый список с результатом.

Марк Байерс
источник
13
Незначительная точка, но это даст IEnumerable<car>, а не List<car>. Вам нужно позвонить, ToList()чтобы получить список обратно. Кроме того, я считаю, что это должно бытьGetSomeOtherList().Except(GetTheList()).ToList()
Адам Робинсон
9
Вам также понадобится, using System.Linq;если у вас его не было раньше.
yellavon
1
Примечание: list1.Except (list2) не даст такой же результат, как list2.Except (list1). Последний работал на меня.
Radbyx
2
Просто будьте осторожны при использовании, так Exceptкак это фактически выполняет операцию установки , которая отличает результирующий список. Я не ожидал такого поведения, поскольку использую, а Listне HashSet. Связанный.
Логан
4
Почему это правильный ответ? Конечно, это может дать вам то, что вы хотите в своем контексте, однако, «Удалить элементы из одного списка в другом», безусловно, не эквивалентно операции разницы набора, и вы не должны дезинформировать людей, принимая это как правильный ответ !!!!
user1935724
37

Вам не нужен индекс, так как List<T>класс позволяет вам удалять элементы по значению, а не индексировать с помощью Removeфункции.

foreach(car item in list1) list2.Remove(item);
Адам Робинсон
источник
3
+1, но IMO вы должны использовать скобки вокруг list2.Remove(item);утверждения.
ANeves
2
@sr pt: Я всегда использую скобки в операторах, которые появляются в другой строке, но не в блоках с одним оператором, которые я могу / делаю в той же строке, что и оператор управления потоком.
Адам Робинсон
4
@uriz: Независимо от квалификации того, что было бы изящно, это единственный ответ, который фактически делает то, что говорит вопрос (удаляет элементы в основном списке); другой ответ создает новый список, который может быть нежелателен, если список передается от другого вызывающего абонента, который ожидает его изменения вместо получения списка замены.
Адам Робинсон
5
@uriz @AdamRobinson, так как мы обсуждаем элегантные решения ...list1.ForEach(c => list2.Remove(c));
Дэвид Шеррет
1
«элегантный» должен означать, что «разработчик, увлеченный поддержкой этого кода, сочтет его простым и легким для понимания», поэтому это лучший ответ.
Сет
22

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

list2 = list2.Except(list1).ToList();

Это, конечно, при условии, что объекты в списке list1, которые вы удаляете из списка list2, являются одним и тем же экземпляром.

Berkshire
источник
2
Также удаляет дубликаты.
Июль,
17

В моем случае у меня было два разных списка с общим идентификатором, вроде внешнего ключа. Второе решение, цитируемое "nzrytmn" :

var result =  list1.Where(p => !list2.Any(x => x.ID == p.ID && x.property1 == p.property1)).ToList();

Был тот, который лучше всего подходит в моей ситуации. Мне нужно было загрузить DropDownList без записей, которые уже были зарегистрированы.

Спасибо !!!

Это мой код:

t1 = new T1();
t2 = new T2();

List<T1> list1 = t1.getList();
List<T2> list2 = t2.getList();

ddlT3.DataSource= list2.Where(s => !list1.Any(p => p.Id == s.ID)).ToList();
ddlT3.DataTextField = "AnyThing";
ddlT3.DataValueField = "IdAnyThing";
ddlT3.DataBind();
Габриэль Сантос Рейс
источник
Ты никогда не объяснял, что такое DDlT3
rogue39nin
15

Вы могли бы использовать LINQ, но я бы пошел с RemoveAllметодом. Я думаю, что именно он лучше выражает ваши намерения.

var integers = new List<int> { 1, 2, 3, 4, 5 };

var remove = new List<int> { 1, 3, 5 };

integers.RemoveAll(i => remove.Contains(i));
Жоао Анжело
источник
9
Или еще проще с группами методов, которые вы можете сделать - inteers.RemoveAll (remove.Contains);
Райан
12
list1.RemoveAll(l => list2.Contains(l));
Александр Амадо де Кастро
источник
он же «абсолютно нечистый» :-)
Ксан-Кун, Кларк-Дэвис,
Что не так с этим. Это выглядит лучше, чем создание другого списка с помощью Except. Особенно, когда оба списка очень маленькие.
Майк Кескинов
1
Поскольку оба метода списка есть O(N), это приведет к тому, O(N^2)что могут возникнуть проблемы с большими списками.
Tigrou
7

Решение 1: Вы можете сделать так:

List<car> result = GetSomeOtherList().Except(GetTheList()).ToList();

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

Решение 2:

List<car> list1 = GetTheList();
List<car> list2 = GetSomeOtherList();

мы притворяемся, что list1 - это ваш основной список, а list2 - ваш вторичный список, и вы хотите получить элементы list1 без элементов list2.

 var result =  list1.Where(p => !list2.Any(x => x.ID == p.ID && x.property1 == p.property1)).ToList();
nzrytmn
источник
0

Так Exceptкак список не изменяется, вы можете использовать ForEach на List<T>:

list2.ForEach(item => list1.Remove(item));

Возможно, это не самый эффективный способ, но он прост, поэтому удобочитаем, и обновляет исходный список (это мое требование).

Necriis
источник
-3

Вот и ты ..

    List<string> list = new List<string>() { "1", "2", "3" };
    List<string> remove = new List<string>() { "2" };

    list.ForEach(s =>
        {
            if (remove.Contains(s))
            {
                list.Remove(s);
            }
        });
Ян П
источник
3
-1. Это вызовет исключение после удаления первого элемента. Кроме того, (в общем случае) лучше обходить список для удаления , поскольку он обычно меньше. Вы также заставляете больше обходов списка делать это таким образом.
Адам Робинсон