fundo
Recentemente, estou no processo de duradouras entrevistas técnicas cansativas para posições que usam a pilha .NET, algumas das quais incluem perguntas tolas como essa e algumas que são mais válidas. Recentemente, deparei com um problema que pode ser válido, mas quero verificar com a comunidade aqui para ter certeza.
Quando perguntado por um entrevistador como eu contaria a frequência das palavras em um documento de texto e classificaria os resultados, respondi que
- Use um objeto de fluxo e coloque o arquivo de texto na memória como uma sequência.
- Divida a string em uma matriz nos espaços, ignorando a pontuação.
- Use LINQ contra a matriz para
.GroupBy()
e.Count()
, em seguida,OrderBy()
disse contagem.
Entendi errado esta resposta por dois motivos:
- A transmissão de um arquivo de texto inteiro na memória pode ser desastrosa. E se fosse uma enciclopédia inteira? Em vez disso, devo transmitir um bloco de cada vez e começar a construir uma tabela de hash.
- O LINQ é muito caro e requer muitos ciclos de processamento. Em vez disso, eu deveria ter criado uma tabela de hash e, para cada iteração, apenas adicionaria uma palavra à tabela de hash se ela não existisse e aumentaria sua contagem.
A primeira razão parece, bem, razoável. Mas o segundo me dá mais pausa. Eu pensei que um dos pontos de venda do LINQ é que ele simplesmente abstrai operações de nível inferior como tabelas de hash, mas que, sob o véu, ainda é a mesma implementação.
Questão
Além de alguns ciclos de processamento adicionais para chamar métodos abstraídos, o LINQ requer significativamente mais ciclos de processamento para realizar uma tarefa de iteração de dados do que uma tarefa de nível inferior (como construir uma tabela de hash) exigiria?
fonte
Respostas:
Eu diria que a principal fraqueza dessa resposta é menos o uso do Linq e mais os operadores específicos escolhidos.
GroupBy
pega cada elemento e o projeta em uma chave e um valor que entram em uma pesquisa. Em outras palavras, cada palavra adicionará algo à pesquisa.A implementação ingênua
.GroupBy(e => e)
armazenará uma cópia de cada palavra no material de origem, tornando a pesquisa final quase tão grande quanto o material de origem original. Mesmo se projetarmos o valor.GroupBy(e => e, e => null)
, estamos criando uma grande pesquisa de pequenos valores.O que queremos é um operador que preserve apenas as informações necessárias, que são uma cópia de cada palavra e a contagem dessa palavra até o momento. Para isso, podemos usar
Aggregate
:A partir daqui, há várias maneiras pelas quais podemos tentar tornar isso mais rápido:
fonte
Eu acho que você teve uma fuga por pouco, o entrevistador realmente não sabia do que estava falando. Pior ainda, ele acredita que há uma resposta "certa". Se ele era alguém em quem você gostaria de trabalhar, eu esperaria que ele tomasse sua resposta inicial, descubra por que você a escolheu e depois o desafie a melhorar se ele encontrar problemas com ela.
O LINQ assusta as pessoas porque parece mágica. Na verdade, é muito simples (tão simples que você precisaria ser um gênio para sugerir isso)
É compilado em:
(Por favor, não grite se eu tenho a sintaxe errada, é depois da meia-noite e estou na cama :))
Onde está um método de extensão no IEnumerable que leva um lambda. O lambda é simplesmente (neste caso) compilado para um delegado:
O método Where simplesmente adiciona TODAS as instâncias do item em que AMethod retorna true a uma coleção retornada.
Não há razão para ser mais lento do que fazer um foreach e adicionar todos os itens correspondentes a uma coleção nesse loop. De fato, o método de extensão Where provavelmente está fazendo exatamente isso. A verdadeira magia vem quando você precisa injetar uma alternativa em que critérios.
Como mencionei acima no meu comentário, alguns dos exemplos vinculados estão muito errados. E é esse tipo de desinformação que causa os problemas.
Finalmente, se a entrevista tivesse lhe dado uma chance, você poderia ter dito o seguinte:
O LINQ é fácil de ler, especialmente onde você começa a apresentar projeções e agrupamentos interessantes. O código de fácil leitura é fácil [y | ier] para manter o código que é uma vitória.
Seria realmente fácil medir o desempenho se fosse realmente um gargalo e substituí-lo por outra coisa.
fonte
Count
alguns cenários estreitos que funcionam mal com o encapsulamento. Concatene uma lista de um milhão de itens e um iterador de quatro itens eCount
deve exigir cerca de 5 operações, mas exigirá um milhão. Desejo que a MS definaIEnhancedEnumerator
com umint Move(int)
método que retorne 0 em caso de sucesso ou retorne a quantidade de déficit em caso de falha (o que seria feitoMove(1000003)
em um recém-criado aList<T>.Enumerator
partir de uma lista de milhões de itens retornaria 3). Qualquer implementação ...IEnumerable<T>
pode ser envolvido em uma implementação deIEnhancedEnumerator
, mas tipos implementadosIEnhancedEnumerator
diretamente podem permitir acelerações de ordem de magnitude em muitas operações, e até coisas como o retornoAppend
podem expor a capacidade de busca rápida dos constituintes.