2015-07-13 10 views
6

Mam tablicę list:jak znaleźć użytkowników, które istnieją w co najmniej dwóch list w liście list

var stringLists = new List<string>[] 
{ 
    new List<string>(){ "a", "b", "c" }, 
    new List<string>(){ "d", "b", "c" }, 
    new List<string>(){ "a", "d", "c" } 
}; 

chcę wyodrębnić wszystkie elementy, które są powszechne w co najmniej 2 list. W tym przykładzie powinienem uzyskać wszystkie elementy: ["a", "b", "c", "d"]. Wiem, jak znaleźć elementy wspólne dla wszystkich, ale nie można wymyślić żadnego rozwiązania tego problemu.

+0

Możesz zacząć [tutaj] (http: // stackoverflow. com/questions/12584179/check-for-any-element-that-exist-in-two-collections) i zmodyfikuj go, aby użyć trzeciej kolekcji. –

+0

Można użyć 'SelectMany', aby utworzyć pojedynczą listę ze wszystkich list, a następnie wybrać wszystkie elementy z co najmniej 2 wystąpieniami. – LInsoDeTeh

+2

czy możliwe są duplikaty na listach? mam na myśli, że lista może zawierać nową listę () {"a", "a", "a"}? –

Odpowiedz

9

Można użyć coś takiego:

var result = stringLists.SelectMany(l => l.Distinct()) 
         .GroupBy(e => e) 
         .Where(g => g.Count() >= 2) 
         .Select(g => g.Key); 

tylko dla zabawy kilka iteracyjne rozwiązania:

var seen = new HashSet<string>(); 
var current = new HashSet<string>(); 
var result = new HashSet<string>(); 
foreach (var list in stringLists) 
{ 
    foreach(var element in list) 
     if(current.Add(element) && !seen.Add(element)) 
      result.Add(element); 

    current.Clear(); 
} 

lub:

var already_seen = new Dictionary<string, bool>(); 
foreach(var list in stringLists) 
    foreach(var element in list.Distinct()) 
     already_seen[element] = already_seen.ContainsKey(element); 

var result = already_seen.Where(kvp => kvp.Value).Select(kvp => kvp.Key); 

lub (zainspirowany Tim's answer) :

int tmp; 
var items = new Dictionary<string,int>(); 

foreach(var str in stringLists.SelectMany(l => l.Distinct())) 
{ 
    items.TryGetValue(str, out tmp); 
    items[str] = tmp + 1; 
} 

var result = items.Where(kv => kv.Value >= 2).Select(kv => kv.Key); 
+0

gr8 anwer próbowałem dwa razy, ale źle się z nim zgadzałem ... wydaje mi się, że działa dobrze. –

+0

Przeoczyłem, że tak proste jest liczyć tylko występowanie na listach, kluczem jest 'Odrębny'. –

0

Wykonaj następujące kroki:

  1. Utwórz słownik elementu -> Lista indeksów
  2. pętla nad wszystkimi listami
  3. dla numeru wykazie I: foreach elementem listy: dodatek I do lista w słowniku na pozycji: dictionary[element].Add(i) (jeśli jeszcze nie jest)
  4. Policz, ile list w słowniku ma dwa wpisy:
-1

Można użyć SelectMany spłaszczyć listy, a następnie wybrać wszystkie elemeents które występują dwa lub więcej:

var singleList = stringLists.SelectMany(p => p); 
var results = singleList.Where(p => singleList.Count(q => p == q) >= 2).Distinct(); 
+1

Awesome guys! Zwolnijmy faceta, który wysłał swoją odpowiedź 30 sekund za późno :-) – LInsoDeTeh

2

można użyć Dictionary<string, int>, klucz jest ciągiem i wartością jest liczba:

Dictionary<string, int> itemCounts = new Dictionary<string,int>(); 
for(int i = 0; i < stringLists.Length; i++) 
{ 
    List<string> list = stringLists[i]; 
    foreach(string str in list.Distinct()) 
    { 
     if(itemCounts.ContainsKey(str)) 
      itemCounts[str] += 1; 
     else 
      itemCounts.Add(str, 1); 
    } 
} 
var result = itemCounts.Where(kv => kv.Value >= 2); 

Używam list.Distinct(), ponieważ chcesz tylko liczyć wystąpienia na różnych listach.

Zgodnie z wnioskiem, o to metoda rozszerzenie, które można ponownie wykorzystać z dowolnym typem:

public static IEnumerable<T> GetItemsWhichOccurAtLeastIn<T>(this IEnumerable<IEnumerable<T>> seq, int minCount, IEqualityComparer<T> comparer = null) 
{ 
    if (comparer == null) comparer = EqualityComparer<T>.Default; 
    Dictionary<T, int> itemCounts = new Dictionary<T, int>(comparer); 

    foreach (IEnumerable<T> subSeq in seq) 
    { 
     foreach (T x in subSeq.Distinct(comparer)) 
     { 
      if (itemCounts.ContainsKey(x)) 
       itemCounts[x] += 1; 
      else 
       itemCounts.Add(x, 1); 
     } 
    } 
    foreach(var kv in itemCounts.Where(kv => kv.Value >= minCount)) 
     yield return kv.Key; 
} 

użycia jest prosty:

string result = String.Join(",", stringLists.GetItemsWhichOccurAtLeastIn(2)); // a,b,c,d 
+0

Zawinęłbym to również w ładną metodę rozszerzenia. –

+2

@YuvalItzchakov: zgodnie z życzeniem :) –

+0

Dobrze, ale dlaczego pętla 'for' zamiast drugiej pętli' foreach'? – sloth