Estou tentando determinar se há uma alteração na equivalência Big O de um loop aninhado ao usar uma seleção LINQ.
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;
}
}
}
Acredito que este exemplo de loop aninhado seja O (n ^ 2) por complexidade.
Substituí o loop aninhado por um LINQ selecione assim:
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;
}
}
Se nada mais, o código parece ser um pouco mais limpo de ler, uma vez que afirma explicitamente "estamos procurando este particular Bar
para trabalhar".
Minha pergunta é a seguinte : O uso do LINQ select reduz a complexidade do Big O?
bar
filtrarbar.PropZ.HasValue
primeiro, se você espera que mais do que uma pequena quantia seja avaliada como falsa? Realmente não responde à sua pergunta, estou apenas revisando seu código.foo.PropA == bar.PropA
é verdadeiro para várias entradasbarList
? Edit: definitivamente não, pois o segundo lançará aNullReferenceException
quando o select retornarnull
..FirstOrDefault()
fará com que o loop linq saia cedo se uma correspondência for encontrada, enquanto seus loops aninhados mudos não saem mais cedo, então sim, eu acho que o linq terá um melhor oh grande. Mas não tenho certeza, portanto, um comentário e não uma resposta.Respostas:
Big O é o mesmo, pois os dois códigos (no pior caso) terão que realizar uma comparação para cada combinação de itens das duas listas. Por exemplo, se não houver correspondências entre as listas, nenhuma otimização na implementação do Linq evitará a necessidade de verificar todos os itens.
Mas ei, você realmente não quer saber sobre big-O, não é? Você quer saber se o segundo é mais rápido . A resposta é sim, a solução baseada em linq é mais rápida (desde que seja grande o suficiente) porque ela só precisa executar a verificação até que a primeira correspondência na lista seja encontrada, enquanto a solução aninhada sempre precisa visitar todos os itens . O
Where
método não varre a lista inteira imediatamente, mas o avaliador cria um iterador lento. OFirstOrDefault
método então executa o iterador até a primeira correspondência ser encontrada, o que significa que, no caso comum, não é necessário percorrer a lista inteira. É claro que há alguma sobrecarga na solução Linq, portanto, se n for baixo o suficiente, o loop for aninhado será mais rápido.Você mesmo pode conferir a fonte: https://github.com/dotnet/corefx/blob/master/src/System.Linq/src/System/Linq/Enumerable.cs
(Mas se você adicionasse um
break
no primeiro exemplo, os dois códigos seriam semanticamente equivalentes e o primeiro seria mais rápido devido a menos sobrecarga.)fonte
Não há diferença na complexidade, ambos são O (n²) porque Onde é O (n) de Linq.
O uso de FirstOrDefault é equivalente a fazer isso:
Isso é uma melhoria no tempo de cálculo, mas não na complexidade.
Você pode fazer melhor usando um Hashmap para uma das duas listas.
O operador Join do Linq criará um Hashmap para uma das 2 listas, para que você ganhe desempenho ao pesquisar dois elementos correspondentes:
Isso deve ser executado em O (n log (n))
fonte
Lookup(Of TKey, TElement)
, iterarbarList
, obter oGrouping
para cada item e adicionar itens aoGrouping
. Em seguida, iterafooList
, obtém oGrouping
e retorna cada elemento do agrupamento. De onde vem o `log (n)?