Как удалить строки в списке из другого списка?

У меня есть 2 списка, имена которых listA и listB.

Я хочу удалить строки в listB, которые есть в listA, но я хочу сделать это следующим образом:

если listA содержит: "bar", "bar", "bar", "foo" и listB содержит: "bar"

он удаляет только 1 бар, и результатом будет: "bar", "bar", "foo"

код, который я написал, удаляет все «бары»:

List<string> result = listA.Except(listB).ToList();

person John Duda    schedule 28.02.2016    source источник
comment
Имеет ли значение сохранение порядка исходного списка?   -  person hatchet - done with SOverflow    schedule 28.02.2016


Ответы (4)


Вы можете попробовать удалить его по одному:

foreach (var word in listB)
    listA.Remove(word);

Метод Remove будет удалять только один элемент за раз и не генерирует исключение (но возвращает false), если элемент не найден: https://msdn.microsoft.com/en-us/library/cd666k3e(v=vs.110).aspx

person Ian    schedule 28.02.2016
comment
Вы можете избежать вызова «Содержит», используя непосредственно IndexOf, чтобы получить позицию - person Steve; 28.02.2016
comment
Это неэффективно, но вы можете заставить его использовать хотя бы напрямую listA.Remove(word). Нет необходимости в Contains. - person Ivan Stoev; 28.02.2016

Вот более эффективный способ сделать это:

var countB = new Dictionary<string, int>(listB.Count);
foreach (var x in listB)
{
    int count;
    countB.TryGetValue(x, out count);
    countB[x] = count + 1;
}
listA.RemoveAll(x =>
{
    int count;
    if (!countB.TryGetValue(x, out count)) return false;
    if (count == 1)
        countB.Remove(x);
    else
        countB[x] = count - 1;
    return true;
});
person Ivan Stoev    schedule 28.02.2016
comment
Вы пропустили шаг, на котором нужно заполнить countB. - person displayName; 29.02.2016
comment
@displayName Не пробовал - попробуй и увидишь (подсказка - строчка countB[x] = count + 1;) :) - person Ivan Stoev; 29.02.2016
comment
О, понятно... не знал о таком поведении TryGetValue() в словарях. Узнал что-то новое. - person displayName; 29.02.2016

Это более быстрый метод, но он, вероятно, изменит порядок элементов первого списка. Шаги:

  • Сопоставьте listA с Dictionary<string, int> (назовем его listAMap), где key — это элемент списка, а value — это общее количество раз, когда это значение встречается в listA;
  • Перебрать listB и для каждого элемента listB, если этот элемент находится в listAMap, уменьшить его количество;
  • Получите ключи listMapA, используя свойство Keys. словарей C# и выполнить итерацию по всем ключам. Для каждого ключа, который имеет положительное значение, добавьте этот ключ в другой список в общем количестве раз. Итак, если запись "bar" -> 2, дважды добавьте «bar» в новый список.

Общее время выполнения алгоритма составляет O(m + n), где m и n — количество элементов в обоих исходных списках. Это лучшее время выполнения, чем другие упомянутые здесь подходы, которые имеют время выполнения O(m * n). Очевидно, что этот алгоритм использует больше места.


Вспомогательный код для алгоритма выше:

//Step-1: Create the dictionary...
var listAMap = new Dictionary<string, int>();
foreach (var listAElement in listA)
{
    listAMap.ContainsKey(listAElement) ? listAMap[listAElement]++ : listAMap.Add(listAElement, 1);
}

// Step-2: Remove the listB elements from dictionary...
foreach (var listBElement in listB)
{
    if (listAMap.Contains(listBElement)) listAMap[listBElement]--;
}

//Step-3: Create the new list from pruned dictionary...
var prunedListA = new List<string>();
foreach (var key in listAMap.Keys)
{
    if (listAMap[key] <= 0) continue;
    for (var count = 0; count < listAMap[key]; count++)
    {
        prunedListA.Add(key);
    }
}

//prunedListA contains the desired elements now.
person displayName    schedule 28.02.2016
comment
Я думал о чем-то подобном, но подсчитывая listB, а затем удаляя элементы из listA tah match (и уменьшая количество совпадений). В любом случае, +1 за размышления об эффективности. - person Ivan Stoev; 28.02.2016
comment
@IvanStoev: Мы не сопоставляем элементы в списке. Мы делаем O(1) поиск в словаре. Серьезно, решение очень простое (не то, чтобы вы могли поставить +2 за ответ). Я тоже должен был добавить код. Будет делать, когда я получаю доступ к SO с ноутбука. - person displayName; 28.02.2016
comment
@IvanStoev: Последнее, что нужно сделать сейчас, это выделить приведенный выше код в отдельный метод, чтобы он был чище. - person displayName; 29.02.2016
comment
Хорошая точка зрения. Но позвольте мне опубликовать алгоритм, который я имел в виду. Как я уже сказал, аналогичная идея O (m + n), но без эффекта переупорядочения, о котором вы говорите. - person Ivan Stoev; 29.02.2016
comment
Кстати, я не планировал публиковать это, потому что большинству людей нравятся однострочники, и их не волнует производительность. Но однажды вы поделились своими мыслями :) - person Ivan Stoev; 29.02.2016
comment
@IvanStoev: Вы правы - людям нравятся однострочные. Читабельность кода очень важна. И в SO, где отвечают несколько программистов, часто становится разницей между лучшим ответом и понравившимся ответом. - person displayName; 29.02.2016

person    schedule
comment
По сути, это копия ответа @Ian, опубликованного за 10+ минут до вашего. - person Ivan Stoev; 28.02.2016