2015-07-13 2 views
6

mi hanno una serie di liste:come trovare i membri che esistono in almeno due liste in una lista di liste

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

Voglio estrarre tutti gli elementi che sono comuni in almeno 2 liste. Quindi, per questo esempio, dovrei ottenere tutti gli elementi ["a", "b", "c", "d"]. So come trovare elementi comuni a tutti ma non riesco a pensare ad alcun modo per risolvere questo problema.

+0

Si può iniziare [qui] (http: // StackOverflow. it/questions/12584179/check-for-any-element-that-exists-in-two-collections) e modificarlo per utilizzare una terza raccolta. –

+0

È possibile utilizzare 'SelectMany' per creare un singolo elenco da tutti gli elenchi e quindi selezionare tutti gli elementi con almeno 2 occorrenze. – LInsoDeTeh

+2

è possibile duplicare nelle liste? Voglio dire è lista nuova lista () {"a", "a", "a"} può verificarsi? –

risposta

9

Si potrebbe usare qualcosa di simile:

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

solo per divertimento alcune soluzioni iterative:

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

o:

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

o (ispirato 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 ho provato due volte ma ho sbagliato tutto il tempo ... questo sembra andare a lavorare pulito soluzione –

+0

Trascurato che è così semplice contare solo l'occorrenza tra gli elenchi, utilizzando 'Distinct' è la chiave. –

0

le seguenti operazioni:

  1. Creare un elemento Dizionario -> Elenco degli indici
  2. loop su tutte le liste
  3. per numero di lista i: elemento foreach nella lista: aggiungere i to l'elenco nel dizionario in posizione: dictionary[element].Add(i) (se non già presente)
  4. Conta quante liste nel dizionario hanno due voci
-1

È possibile utilizzare SelectMany per appiattire la lista e poi raccogliere tutti elemeents che si verificano due o più volte:

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

Ragazzi fantastici! Facciamo un downvote al tizio che ha postato la sua risposta trenta secondi troppo tardi :-) – LInsoDeTeh

2

si potrebbe usare una Dictionary<string, int>, la chiave è la stringa e il valore è il conteggio:

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

Io uso list.Distinct() poiché si desidera contare le occorrenze in elenchi diversi.

Come richiesto, ecco un metodo di estensione che è possibile riutilizzare con qualsiasi tipo:

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

L'uso è semplice:

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

Lo avrei avvolto anche in un bel metodo di estensione. –

+2

@YuvalItzchakov: come richiesto :) –

+0

Bello, ma perché un ciclo 'for' invece di un secondo ciclo' foreach'? – sloth