Ilookup vs. Igrouping
Pergunta
Estou tendo problemas para articular as diferenças entre ILookup<TKey, TVal>
e IGrouping<TKey, TVal>
, e estou curioso se eu entender corretamente agora. Linq agravou a questão produzindo sequências de IGrouping
itens e também me dando um ToLookup
Método de extensão. Então, parecia que eles eram iguais até eu parecer mais de perto.
var q1 =
from n in N
group n by n.MyKey into g
select g;
// q1 is IEnumerable<IGrouping<TKey, TVal>>
Que é equivalente a:
var q2 = N.GroupBy(n => n.MyKey, n => n);
// q2 is IEnumerable<IGrouping<TKey, TVal>>
O que se parece muito com:
var q3 = N.ToLookup(n => n.MyKey, n => n);
// q3 is ILookup<TKey, TVal>
Estou correto nas seguintes analogias?
- Um
IGrouping<TKey, TVal>
é um único grupo (ou seja, uma sequência com chave), análoga aKeyValuePair<TKey, TVal>
onde o valor é realmente uma sequência de elementos (em vez de um único elemento) - Um
IEnumerable<IGrouping<TKey, TVal>>
é uma sequência daqueles (semelhante ao que você recebe quando itera sobre umIDictionary<TKey, TVal>
- Um
ILookup<TKey, TVal>
é mais como umIDictionary<TKey, TVal>
onde o valor é realmente uma sequência de elementos
Solução
Sim, tudo isso está correto.
E ILookup<TKey, TValue>
também se estende IEnumerable<IGrouping<TKey, TValue>>
Assim, você pode iterar em todos os pares de chave/coleção, bem como (ou em vez de) apenas procurar as teclas específicas.
Eu basicamente penso em ILookup<TKey,TValue>
como sendo como IDictionary<TKey, IEnumerable<TValue>>
.
Ter em mente que ToLookup
é uma operação "faça agora" (execução imediata), enquanto um GroupBy
é adiado. Por acaso, com a maneira como "Pull Linq" funciona, quando você começa a puxar IGrouping
s do resultado de um GroupBy
, ele precisa ler todos os dados de qualquer maneira (porque você não pode mudar o grupo no meio do caminho), enquanto em outras implementações poderá produzir um resultado de streaming. (Isso faz em Push Linq; eu esperaria que o LINQ fosse o mesmo.)
Outras dicas
Há outra diferença importante entre Ilookup e Idictionary: o primeiro aplica imutabilidade no sentido de que aqui não há métodos para alterar os dados (exceto quando o consumidor executa um elenco explícito). Por outro lado, o Idictionary possui métodos como "Adicionar" que permitem alterar os dados. Portanto, da perspectiva da programação funcional e/ou da programação paralela, o iLookup é melhor. (Eu gostaria que houvesse também uma versão do Ilookup que atribua apenas um valor a uma chave em vez de um grupo.)
(BTW., Parece valer a pena apontar que a relação entre ienumerable e ilist é um pouco semelhante à de Ilookup e Idictionary - o primeiro é imutável, o último não é.)
GroupBy
e ToLookUp
tem quase a mesma funcionalidade EXCETO isto: Referência
Grupo: O operador do Grupoby retorna grupos de elementos com base em algum valor -chave. Cada grupo é representado por objeto igrupo.
TOLOOKUP: Tolookup é o mesmo que Groupby; A única diferença é que a execução do grupo é adiada, enquanto a execução de Tolookup é imediata.
Vamos limpar a diferença usando o código de amostra. Suponha que tenhamos uma classe representando Person
modelo:
class Personnel
{
public int Id { get; set; }
public string FullName { get; set; }
public int Level { get; set; }
}
Depois disso, definimos uma lista de personnels
como abaixo:
var personnels = new List<Personnel>
{
new Personnel { Id = 1, FullName = "P1", Level = 1 },
new Personnel { Id = 2, FullName = "P2", Level = 2 },
new Personnel { Id = 3, FullName = "P3", Level = 1 },
new Personnel { Id = 4, FullName = "P4", Level = 1 },
new Personnel { Id = 5, FullName = "P5", Level =2 },
new Personnel { Id = 6, FullName = "P6", Level = 2 },
new Personnel { Id = 7, FullName = "P7", Level = 2 }
};
Agora eu preciso obter o personnels
agrupados pelo seu nível. Eu tenho duas abordagens aqui. usando GroupBy
ou ToLookUp
. Se eu usar GroupBy
, como afirmado anteriormente, ele usará a execução diferida, isso significa que, quando você itera através da coleção, o próximo item pode ou não ser calculado até que seja necessário.
var groups = personnels.GroupBy(p => p.Level);
personnels.RemoveAll(p => p.Level == 1);
foreach (var product in groups)
{
Console.WriteLine(product.Key);
foreach (var item in product)
Console.WriteLine(item.Id + " >>> " + item.FullName + " >>> " + item.Level);
}
No código acima, agrupei primeiro o personnels
, mas antes de itera, eu removi alguns personnels
. Como GroupBy
usa a execução diferida, portanto o resultado final não incluirá os itens removidos, porque o agrupamento será computado no foreach
aponte aqui.
Resultado:
2
2 >>> P2 >>> 2
5 >>> P5 >>> 2
6 >>> P6 >>> 2
7 >>> P7 >>> 2
Mas se eu reescrever o código acima como abaixo: (observe que o código é o mesmo que o código anterior, exceto GroupBy
é substituído por ToLookUp
)
var groups = personnels.ToLookup(p => p.Level);
personnels.RemoveAll(p => p.Level == 1);
foreach (var product in groups)
{
Console.WriteLine(product.Key);
foreach (var item in product)
Console.WriteLine(item.Id + " >>> " + item.FullName + " >>> " + item.Level);
}
Como ToLookUp
usa execução imediata, significa que quando eu chamo o ToLookUp
Método, o resultado é gerado e o grupo é aplicado; portanto, se eu remover algum item de personnels
Antes da iteração, isso não afetará o resultado final.
Resultado:
1
1 >>> P1 >>> 1
3 >>> P3 >>> 1
4 >>> P4 >>> 1
2
2 >>> P2 >>> 2
5 >>> P5 >>> 2
6 >>> P6 >>> 2
7 >>> P7 >>> 2
Observação: GroupBy
e ToLookUp
Ambos retornam tipos diferentes também.
Você pode usar o ToDictionary em vez de Tolookup, mas precisa prestar atenção a isso :(referência)
O uso de tolookup () é muito semelhante ao do Todictionary (), ambos permitem especificar seletores de chave, seletores de valor e comparadores. A principal diferença é que o tolookup () permite (e espera) as teclas duplicadas, enquanto o Todictionary () não