Neste desafio, aprenderemos sobre tabelas hash. Uma tabela hash é usada para implementar arrays associativos ou mapeamentos de pares chave-valor, como os objetos e mapas que temos estudado. Um objeto JavaScript pode ser implementado como uma tabela hash, por exemplo (a implementação de fato dependerá do ambiente em que estiver rodando). O modo como uma tabela hash funciona é recebendo uma entrada de chave e fazendo o hash dessa chave de modo determinístico para algum valor numérico. Este valor numérico é então usado como a chave real através da qual o valor associado é armazenado. Depois, se você tentar acessar a mesma chave novamente, a função de hashing processará a chave, retorna o mesmo resultado numérico e ele será usado para procurar o valor associado. Isto proporciona um tempo de pesquisa O(1) muito eficiente, em média.
As tabelas hash podem ser implementadas como arrays com funções de hash produzindo índices de array dentro de um intervalo especificado. Nesse método, a escolha do tamanho do array é importante, assim como a função de hashing. Por exemplo, o que aconteceria se a função de hashing produzisse o mesmo valor para duas chaves diferentes? A isso chamamos de colisão. Uma maneira de lidar com colisões é apenas armazenar os dois pares chave-valor naquele índice. Então, ao consultar qualquer um, você teria que iterar através do grupo de itens para encontrar a chave que está procurando. Uma boa função de hashing minimizará colisões para manter o tempo de busca eficiente.
Vamos criar a funcionalidade básica de uma tabela hash. Criamos uma função de hashing simples para sua utilização. Você pode passar um valor de string para a função `hash` e ele retornará um valor de hash que você pode usar como chave para o armazenamento. Armazene itens baseados neste valor de hash no objeto `this.collection`. Crie esses três métodos: `add`, `remove` e `lookup`. O primeiro deve aceitar um par de chave-valor para adicionar à tabela hash. O segundo deve remover um par de chave-valor quando recebe uma chave. O terceiro deve aceitar uma chave e retornar o valor associado ou `null` se a chave não estiver presente.