Esta es mi primera pregunta en esta comunidad y me gustaría obtener una respuesta a mi problema. Supongamos que tengo dos listas A y B:
List<int> listA = new List<int>(); List<int> listB = new List<int>(); listA.Add(1); listA.Add(2); listA.Add(8); listB.Add(1); listB.Add(1); listB.Add(1); listB.Add(2); listB.Add(2); listB.Add(2); listB.Add(2);
Me gustaría contar las ocurrencias de cada elemento en listB que ya existía en listA y agregar cero si no existe:
Esto es lo que he probado:
var result = listB.GroupBy(x => x).ToDictionary(k => k.Key, v => v.Count()); foreach(var res in result.Values) Console.WriteLine(res); // it will print only {3,4}
El resultado esperado será:
// { 3,4,0} => 3 is the occurrences number of 1 , 4 is the occurrences number of 2 ,and 0 is the number occurrences of 8
¿Cómo puedo lograr este resultado?
Es un buen problema usar la combinación externa izquierda en LINQ. Aquí hay un artículo al respecto . Es la mejor solución de rendimiento porque itera sobre la colección solo una vez y es importante para colecciones grandes o métodos de uso frecuente.
Aquí hay un ejemplo para su problema:
var result = from a in listA join b in listB.GroupBy(x => x) on a equals b.Key into gj from subList in gj.DefaultIfEmpty() select new { Number = a, Count = subList?.Count() ?? 0 };
También puede usar GroupJoin y el encadenamiento de métodos, pero creo que es mucho más difícil de leer:
var result = listA .GroupJoin(listB.GroupBy(x => x), a => a, b => b.Key, (a, gj) => new { a, gj }) .SelectMany(@t => @t.gj.DefaultIfEmpty(), (@t, subList) => new { Number = @ta, Count = subList?.Count() ?? 0 });
Después de ese resultado, contendrá una colección de tipos anónimos con campos Number
y Count
con 3 elementos: {1, 3}, {2, 4}, {8, 0}
.
Actualización de los comentarios (gracias a Caius Jard): si no puede usar el operador condicional nulo, puede reemplazarlo con una verificación explícita: subList == null ? 0 : subList.Count()
Usaría la extensión linq .Count(), que contará la cantidad de elementos que cumplen una condición. Sí, esto iterará la lista más veces de las necesarias, pero no crea ningún objeto innecesario y es muy legible:
var countOccurences = new List<int>(); foreach (var inA in listA) { countOccurences.Add(listB.Count(inB => inB == inA)); } Console.WriteLine(string.Join(", ", countOccurences));
Una vez que tenga el ciclo funcionando, entonces debería ser fácil ver que se puede hacer en una sola declaración:
var countOccurences = listA.Select(inA => listB.Count(inB => inA == inB));
arreglando tu código
var result = listB.GroupBy(x => x).ToDictionary(k => k.Key, v => v.Count()); foreach(var ent in listA) Console.WriteLine(result.ContainsKey(ent)?result[ent]:0);
declaración Linq de una línea
listA.Select(a => listB.Contains(a) ? a : 0 ).Select(r=>listB.Count(w=> w==r)).ToList();