Eficiência de dicionários C #

13

Os dicionários C # são uma maneira simples de descobrir se algo existe, etc. etc. Eu tenho uma pergunta sobre como eles funcionam. Digamos que, em vez de um dicionário, eu use um ArrayList. Em vez de usar ContainsKey(ou um método equivalente em outro idioma), percorro o ArrayList para verificar se existe algo lá (ou executando uma pesquisa binária se os dados são classificados ou algo semelhante). Qual a diferença de eficiência? O ContainsKeymétodo está usando uma maneira mais eficiente, em vez de percorrer as chaves e verificar se o que estou procurando existe?

Se digamos que eu criei uma função hash específica que corresponde ao tipo de dados que estou tendo e foi especificamente projetada para esse conjunto de dados, então sim, essa função hash é realmente mais rápida do que repetir os dados. Mas os dicionários são gerais. O método ContainsKey não é específico dos dados que obtém, é um método de pesquisa geral.

Basicamente, o que estou perguntando é. Dicionários são úteis para programadores. Eles incluem métodos que ajudam com muitas coisas e combinam seqüências de caracteres com números inteiros (chaves e valores) e muito mais. Mas com relação à eficiência, o que eles oferecem? Qual é a diferença em ter um dictionaryvs um ArrayListdestructs(string,int)

John Demetriou
fonte
Você está realmente comparando maçãs com laranjas aqui. Eu acho que a palavra-chave que você está procurando Data Structures Este link wiki pode ser de mais ajuda para você
Ampt

Respostas:

20

Você precisa cavar um pouco para ver como o Dicionário é implementado em C # - não é tão óbvio quanto o HashMap (uma tabela de hash) ou o TreeMap (uma árvore classificada) (ou ConcurrentSkipListMap - uma lista de ignorados ).

Se você se aprofundar na seção "Comentários":

A classe genérica Dictionary fornece um mapeamento de um conjunto de chaves para um conjunto de valores. Cada adição ao dicionário consiste em um valor e sua chave associada. A recuperação de um valor usando sua chave é muito rápida, próxima a O (1), porque a classe Dictionary é implementada como uma tabela de hash.

E aí temos que. É uma tabela de hash . Observe que eu vinculei o artigo da Wikipedia lá - é uma leitura bastante boa. Você pode ler a seção sobre resolução de colisão. É possível obter um conjunto de dados patológicos em que a pesquisa retorne para O (N) (por exemplo, tudo o que você insere cai no mesmo valor ou índice de hash na tabela de hash por algum motivo e fica com a verificação linear ).

Embora o Dictionary seja uma solução de uso geral, você não deve usar tipos concretos (como o Dictionary) - você deve usar as interfaces. Nesse caso, essa interface é IDictionary( docs ). Para isso, você é perfeitamente capaz de escrever sua própria implementação de dicionário que faz as coisas da melhor maneira possível para os dados que você possui.

Quanto à eficiência de várias pesquisas / contém?

  • Percorrendo uma lista não classificada: O (N)
  • Pesquisa binária de uma matriz classificada: O (log N)
  • Árvore classificada: O (log N)
  • Tabela de hash: O (1)

Para a maioria das pessoas, a tabela de hash é o que elas desejam.

Você pode achar que o SortedDictionary é o que você deseja:

A SortedDictionary<TKey, TValue>classe genérica é uma árvore de pesquisa binária com recuperação de O (log n), em que n é o número de elementos no dicionário. A esse respeito, é semelhante à SortedList<TKey, TValue>classe genérica. As duas classes têm modelos de objetos semelhantes e ambas têm recuperação O (log n).

Porém, novamente, se a estrutura de dados não é aquela que funciona idealmente com seus dados, você recebe as ferramentas (as interfaces) para poder escrever uma que funcione melhor para seus dados.

O dicionário em si é um tipo de dados abstrato . Você me fornece um dicionário e eu sei o que posso fazer com ele e todas as ferramentas existentes para que eu possa usar pela natureza de ser um dicionário. Se você me desse uma ArrayList, eu me veria escrevendo meu próprio código para pesquisar, inserir ou excluir itens da lista. Isso desperdiça meu tempo e também significa que há mais chances de ocorrer um erro, pois copio o código repetidamente de um local para outro.

Robert Harvey
fonte
5
O (1) não é necessariamente "rápido". Fazer um loop em uma lista ainda pode ser mais rápido que uma hashtable para os tamanhos de coleção com os quais o aplicativo está lidando.
Whatsisname
5
@whatsisname em nenhum momento afirmo que O (1) é rápido. Certamente tem o potencial de ser o mais rápido. A iteração sobre as chaves de uma hashtable é mais lenta que a de um ArrayList (a menos que você esteja usando algo como o LinkedHashMap que o Java fornece). É importante conhecer seus dados e como eles se comportam e escolher a coleção apropriada para eles - e se isso não existir, escreva-o. Supondo, é claro, que esse esforço realmente valha a pena (perfil primeiro!).
Sua cotação diz "A recuperação de um valor usando sua chave é muito rápida, próxima a O (1), porque a classe Dictionary é implementada como uma tabela de hash.", Portanto, o OP pode confundir os dois conceitos. Em outras palavras, eu queria deixar claro que o grande O não conta toda a história sobre "velocidade".
Whatsisname
3
@whatsisname direto da Microsoft. Usar uma chave para procurar um valor, a menos que você tenha uma hashtable patológica (que resolve colisões de hash com algum outro mecanismo) será mais rápida do que procurá-la em uma árvore ou lista classificada (ou lista não classificada). Java, por exemplo, usa sondagem linear (etapa 1) para sua resolução de colisão - que pode ser mais lenta nos casos em que a tabela está muito cheia ou muitos hashes colidem. Para o caso geral, porém, é bom o suficiente.
Como um exemplo relevante, recentemente otimizei algum código em c ++ que originalmente usava uma tabela de hash para conjuntos de dados de cerca de 20 entradas e estava demorando cerca de 400ms para ser concluído. Mudar para uma árvore binária reduziu isso para 200ms, porque a árvore é mais fácil de acessar. Mas pude aprofundar ainda mais o assunto usando uma matriz de pares de valores de nomes e uma função de pesquisa heurística que adivinhou por onde começar a procurar com base em padrões de acesso anteriores. Portanto, é tudo uma questão de quantos dados existem e que tipos de padrões existem nos acessos (por exemplo, localidade).
Jules