Estoy tratando de determinar si hay un cambio en la equivalencia Big O de un bucle anidado cuando uso una selección LINQ en su lugar.
public void myFunc(List<Foo> fooList, List<Bar> barList)
{
foreach(Foo foo in fooList)
{
foreach(Bar bar in barList)
{
if(foo.PropA == bar.PropA && bar.PropZ.HasValue)
foo.PropC = foo.PropB * bar.PropZ;
}
}
}
Creo que este ejemplo de bucle anidado es O (n ^ 2) por complejidad.
Reemplacé el bucle anidado con una selección LINQ como esta:
public void myFunc(List<Foo> fooList, List<Bar> barList)
{
foreach(Foo foo in fooList)
{
Bar bar = (from b in barList
where foo.PropA == b.PropA
select b).FirstOrDefault();
if(bar.PropZ.HasValue)
foo.PropC = foo.PropB * bar.PropZ;
}
}
Por lo menos, el código parece ser un poco más claro para leer, ya que dice explícitamente "estamos buscando este particular Bar
para trabajar".
Mi pregunta es esta : ¿el uso de LINQ select reduce la complejidad de Big O?
bar
s y filtrarbar.PropZ.HasValue
primero, si espera que más de una pequeña cantidad se evalúe como falsa? Realmente no responde a su pregunta, solo estoy revisando su código.foo.PropA == bar.PropA
es cierto para entradas múltiples enbarList
? Editar: definitivamente no, ya que el segundo arrojará unNullReferenceException
cuando regrese la selecciónnull
..FirstOrDefault()
hará que el bucle linq salga temprano si se encuentra una coincidencia, mientras que sus bucles anidados tontos no salen antes, así que sí, creo que linq tendrá un mejor big-oh. Pero no estoy seguro, de ahí un comentario y no una respuesta.Respuestas:
Big O es igual, ya que ambos códigos (en el peor de los casos) deberán realizar una comparación para cada combinación de elementos de ambas listas. Por ejemplo, si no hay coincidencias entre las listas, ninguna optimización en la implementación de Linq evitará tener que verificar todos los elementos.
Pero bueno, realmente no quieres saber sobre big-O, ¿verdad? Desea saber si el segundo es más rápido . La respuesta es sí, la solución basada en linq es más rápida (siempre que sea lo suficientemente grande) porque solo tiene que realizar la verificación hasta que se encuentre la primera coincidencia en la lista, mientras que la solución anidada siempre debe visitar todos los elementos . El
Where
método no escanea toda la lista de inmediato, pero el evaluador crea un iterador vago. ElFirstOrDefault
método ejecuta entonces el repetidor hasta que se encuentre el primer partido, lo que significa que, en el caso común que no tiene que recorrer toda la lista. Por supuesto, hay algo de sobrecarga en la solución de Linq, por lo que si n es lo suficientemente bajo, el bucle anidado será más rápido.Puede consultar la fuente usted mismo: https://github.com/dotnet/corefx/blob/master/src/System.Linq/src/System/Linq/Enumerable.cs
(Pero si agregara un
break
en la primera muestra, los dos códigos serían semánticamente equivalentes, y el primero sería más rápido debido a una menor sobrecarga).fuente
No hay diferencia en la complejidad, ambos son O (n²) porque el Dónde es O (n) de Linq.
El uso de FirstOrDefault es equivalente a hacer esto:
Esta es una mejora en el tiempo de cálculo, pero no en la complejidad.
Puede hacerlo mejor usando un Hashmap para una de las dos listas.
El operador Unirse de Linq creará un Hashmap para una de las 2 listas, por lo que ganará rendimiento al buscar dos elementos coincidentes:
Esto debería ejecutarse en O (n log (n))
fuente
Lookup(Of TKey, TElement)
, iterarbarList
, obtener elGrouping
para cada elemento y agregar elementos alGrouping
. Luego iterafooList
, obtiene elGrouping
y devuelve cada elemento en la agrupación. ¿De dónde viene el `log (n)?