2013-04-04 17 views
7

Mam dwóch list:Różnica pomiędzy dwoma listami zachowaniu duplikaty

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

i chciałbym produkować listę jak

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

Jeżeli lista jest wszystkie elementy z list1 usunięte z list2 I nie sądzę, że istnieje metoda rozszerzenia Linq, ponieważ Except usuwa duplikaty.

Non-LINQ sposób to zrobić byłoby:

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

ale zastanawiam się, czy istnieje metoda rozszerzenie LINQ że mógłbym przegapić.

Edit:

Ponieważ zapewne nie ma żadnych oto metodę rozszerzenia zrobiłem.

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

Zastosowanie:

list1.RemoveRange(list2) 

Odpowiedz

2

Patrząc na przykład, że masz na myśli „wszystkie elementy z listy2 usunięty z listy1”:

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

Listy mają dowolny rozmiar, więc jeden z nich może być większy. – Romoku

+0

Nie ma żadnych założeń dotyczących względnej wielkości list. – Ani

+0

Och, domyślam się, że źle rozumiem, co masz na myśli przez "wszystkie elementy z listy2 usunięte z listy1" – Romoku

1

Nie LINQ, ale jedna linia i tak:

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

btw ... byłoby miło, gdyby List<int> coś jak AddRange jednak usunąć pęczek przedmiotów w tym samym czasie.

1

Jeśli nie dbają o kolejności, w jakiej elementy wyniku przyjść, można to zrobić z LINQ na 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(); 

Oto demo on ideone.

Muszę przyznać, że twoje rozwiązanie jest o wiele prostsze niż moje, więc powinno być traktowane jako zwykła ciekawość, sposób na udowodnienie, że można to zrobić za pomocą LINQ.

Oto jak to działa: dla każdego elementu z pierwszej listy dodajemy parę klucz-wartość z 1; dla każdego elementu z drugiej listy dodajemy parę klucz-wartość z -1. Następnie grupujemy wszystkie elementy według ich klucza, sumujemy ich i negatywne, i produkujemy tyle kluczy, ile suma, upewniając się, że niczego nie wybieramy, gdy wynik jest ujemny.

+0

Dobrze to działa, ale to jest po prostu o wiele bardziej skomplikowana. – Romoku

+0

@dasblinkenlight zamień go w rozszerzenie, aby go uprościć. –

+2

Myślę, że czasami Linq po prostu nie jest odpowiedzią. – Romoku

Powiązane problemy