2013-04-04 5 views
7

У меня есть два списка:разница между двумя списками сохранением дубликатами

var list1 = new List<string> { "A", "A", "B", C" }; 
var list2 = new List<string> { "A", "B" }; 

, и я хотел бы получить список как

var result = new[] { "A", "C" }; 

Если список всех элементов из list1 удален из list2 I не думаю, что для этого существует метод расширения Linq, так как Except удаляет дубликаты.

Не-LINQ способ сделать это было бы:

var tempList = list1.ToList(); 
foreach(var item in list2) 
{ 
    tempList.Remove(item); 
} 

, но мне интересно, если есть способ расширения Linq, что я, возможно, пропустили.

Edit:

Поскольку, вероятно, не являются какой-либо здесь метод расширения я сделал.

public static class LinqExtensions 
{ 
    public static IEnumerable<T> RemoveRange<T>(this IEnumerable<T> source, IEnumerable<T> second) 
    { 
     var tempList = source.ToList(); 

     foreach(var item in second) 
     { 
      tempList.Remove(item); 
     } 

     return tempList; 
    } 

    public static IEnumerable<TFirst> RemoveMany<TFirst, TSecond>(this IEnumerable<TFirst> source, IEnumerable<TSecond> second, Func<TSecond, IEnumerable<TFirst>> selector) 
    { 
     var tempList = source.ToList(); 

     foreach(var item in second.SelectMany(selector)) 
     { 
      tempList.Remove(item); 
     } 

     return tempList; 
    } 
} 

Использование:

list1.RemoveRange(list2) 

ответ

2

Глядя на ваш пример, я думаю, что вы имеете в виду «все элементы из list2 удалены из list1»:

var lookup2 = list2.ToLookup(str => str); 

var result = from str in list1 
      group str by str into strGroup 
      let missingCount 
        = Math.Max(0, strGroup.Count() - lookup2[strGroup.Key].Count()) 
      from missingStr in strGroup.Take(missingCount) 
      select missingStr; 
+0

Списки произвольно по размеру, поэтому любой из них может быть больше. – Romoku

+0

Здесь нет предположений относительно относительных размеров списков. – Ani

+0

О, я думаю, я ошибаюсь, что вы подразумеваете под «всеми элементами из списка2, удаленными из списка1». – Romoku

1

не LINQ, но одна линия в любом случае:

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

... Кстати, было бы неплохо, если бы List<int> было что-то вроде AddRange, но удалить куча предметов одновременно.

1

Если вы не заботитесь о том порядке, в котором элементы результата приходят, вы можете сделать это с LINQ-х GroupBy:

var a = new List<string>{"A","A", "B", "C"}; 
var b = new List<string>{"A", "B"}; 
var res = a.Select(e => new {Key=e, Val=1}) 
    .Concat(b.Select(e => new {Key=e, Val=-1})) 
    .GroupBy(e => e.Key, e => e.Val) 
    .SelectMany(g => Enumerable.Repeat(g.Key, Math.Max(0, g.Sum()))) 
    .ToList(); 

Вот demo on ideone.

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

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

+0

Ну это работает, но это только гораздо сложнее. – Romoku

+0

@dasblinkenlight превращается в расширение для его упрощения. –

+2

Я думаю, что иногда Linq просто не является ответом. – Romoku

Смежные вопросы