Intersección de listas múltiples con IEnumerable.Intersect ()

Tengo una lista de listas en la que quiero encontrar la intersección de esta manera:

var list1 = new List() { 1, 2, 3 }; var list2 = new List() { 2, 3, 4 }; var list3 = new List() { 3, 4, 5 }; var listOfLists = new List<List>() { list1, list2, list3 }; // expected intersection is List() { 3 }; 

¿Hay alguna manera de hacer esto con IEnumerable.Intersect ()?

EDITAR: Debería haber sido más claro en esto: realmente tengo una lista de listas, no sé cuántas habrá, las tres listas anteriores son solo un ejemplo, lo que tengo es en realidad un IEnumerable<IEnumerable>

SOLUCIÓN

Gracias por todas las excelentes respuestas. Resultó que había cuatro opciones para resolver esto: List + aggregate (@Marcel Gosselin), List + foreach (@JaredPar, @Gabe Moothart), HashSet + aggregate (@jesperll) y HashSet + foreach (@Tony the Pony). Realicé algunas pruebas de rendimiento en estas soluciones ( número variable de listas , número de elementos en cada lista y tamaño máximo de número aleatorio .

Resulta que para la mayoría de las situaciones el HashSet funciona mejor que la Lista (excepto con listas grandes y un tamaño de número aleatorio pequeño, debido a la naturaleza de HashSet, supongo). No pude encontrar ninguna diferencia real entre el método foreach y el agregado método (el método foreach funciona un poco mejor).

Para mí, el método agregado es realmente atractivo (y lo interpreto como la respuesta aceptada), pero no diría que es la solución más fácil de leer. Gracias de nuevo a todos.

Qué tal si:

 var intersection = listOfLists .Skip(1) .Aggregate( new HashSet(listOfLists.First()), (h, e) => { h.IntersectWith(e); return h; } ); 

De esta forma, se optimiza utilizando el mismo HashSet en todo momento y aún en una sola statement. Solo asegúrese de que listOfLists siempre contenga al menos una lista.

De hecho, puedes usar Intersect dos veces. Sin embargo, creo que esto será más eficiente:

 HashSet hashSet = new HashSet(list1); hashSet.IntersectWith(list2); hashSet.IntersectWith(list3); List intersection = hashSet.ToList(); 

No es un problema con series pequeñas, por supuesto, pero si tienes muchos sets grandes, podría ser significativo.

Básicamente, Enumerable.Intersect necesita crear un conjunto en cada llamada; si sabe que va a hacer más operaciones de conjunto, también puede mantener ese conjunto.

Como siempre, manténgase atento al rendimiento frente a la legibilidad: el método de encadenamiento de llamar a Intersect dos veces es muy atractivo.

EDITAR: Para la pregunta actualizada:

 public List IntersectAll(IEnumerable> lists) { HashSet hashSet = null; foreach (var list in lists) { if (hashSet == null) { hashSet = new HashSet(list); } else { hashSet.IntersectWith(list); } } return hashSet == null ? new List() : hashSet.ToList(); } 

O si sabe que no estará vacío, y que Skip será relativamente barato:

 public List IntersectAll(IEnumerable> lists) { HashSet hashSet = new HashSet(lists.First()); foreach (var list in lists.Skip(1)) { hashSet.IntersectWith(list); } return hashSet.ToList(); } 

Prueba esto, funciona, pero realmente me gustaría deshacerme de .ToList () en conjunto.

 var list1 = new List() { 1, 2, 3 }; var list2 = new List() { 2, 3, 4 }; var list3 = new List() { 3, 4, 5 }; var listOfLists = new List>() { list1, list2, list3 }; var intersection = listOfLists.Aggregate((previousList, nextList) => previousList.Intersect(nextList).ToList()); 

Actualizar:

Siguiendo el comentario de @pomber, es posible deshacerse de ToList() dentro de la llamada Aggregate y moverlo afuera para ejecutarlo solo una vez. No probé el rendimiento si el código anterior es más rápido que el nuevo. El cambio necesario es especificar el parámetro de tipo genérico del método Aggregate en la última línea como se muestra a continuación:

 var intersection = listOfLists.Aggregate>( (previousList, nextList) => previousList.Intersect(nextList) ).ToList(); 

Podrías hacer lo siguiente

 var result = list1.Intersect(list2).Intersect(list3).ToList(); 

Esta es mi versión de la solución con un método de extensión al que llamé IntersectMany.

 public static IEnumerable IntersectMany(this IEnumerable source, Func> selector) { using (var enumerator = source.GetEnumerator()) { if(!enumerator.MoveNext()) return new TResult[0]; var ret = selector(enumerator.Current); while (enumerator.MoveNext()) { ret = ret.Intersect(selector(enumerator.Current)); } return ret; } } 

Entonces el uso sería algo como esto:

 var intersection = (new[] { list1, list2, list3 }).IntersectMany(l => l).ToList(); 

Esta es mi solución de una sola fila para List of List (ListOfLists) sin intersectar función:

 var intersect = ListOfLists.SelectMany(x=>x).Distinct().Where(w=> ListOfLists.TrueForAll(t=>t.Contains(w))).ToList() 

Esto debería funcionar para .net 4 (o posterior)

Después de buscar en la red y no encontrar realmente algo que me gustara (o que funcionara), dormí y pensé en esto. El mío usa una clase ( SearchResult ) que tiene un EmployeeId y eso es lo que necesito que sea común en todas las listas. Devuelvo todos los registros que tienen un EmployeeId . De EmployeeId en cada lista. No es lujoso, pero es simple y fácil de entender, justo lo que me gusta. Para listas pequeñas (mi caso) debería funcionar bien, ¡y cualquiera puede entenderlo!

 private List GetFinalSearchResults(IEnumerable> lists) { Dictionary oldList = new Dictionary(); Dictionary newList = new Dictionary(); oldList = lists.First().ToDictionary(x => x.EmployeeId, x => x); foreach (List list in lists.Skip(1)) { foreach (SearchResult emp in list) { if (oldList.Keys.Contains(emp.EmployeeId)) { newList.Add(emp.EmployeeId, emp); } } oldList = new Dictionary(newList); newList.Clear(); } return oldList.Values.ToList(); } 

Aquí hay un ejemplo simplemente usando una lista de entradas, no una clase (esta fue mi implementación original).

 static List FindCommon(List> items) { Dictionary oldList = new Dictionary(); Dictionary newList = new Dictionary(); oldList = items[0].ToDictionary(x => x, x => x); foreach (List list in items.Skip(1)) { foreach (int i in list) { if (oldList.Keys.Contains(i)) { newList.Add(i, i); } } oldList = new Dictionary(newList); newList.Clear(); } return oldList.Values.ToList(); } 

Esta es una solución simple si sus listas son todas pequeñas. Si tiene listas más grandes, no es tan bueno como el conjunto de hash:

 public static IEnumerable IntersectMany(this IEnumerable> input) { if (!input.Any()) return new List(); return input.Aggregate(Enumerable.Intersect); }