domingo, 29 de setembro de 2013

Rotinas para Tabela de Dispersão Encadeada

                Tendo completado três assuntos básicos sobre o tema, a noção do que realiza uma tabela de dispersão, como a função de espalhamento distribui elementos e o tratamento de colisão com encadeamento, será apresentado um código que utiliza um exemplo que também foi discutido anteriormente.

Como pré-requisito, recomenda-se a leitura de lista encadeada genérica parte 1 e parte 2, para evitar prolongar o assunto, será utilizado o mesmo aplicativo que carrega as palavras de um texto, alterando o mesmo para utilizar uma tabela de dispersão em vez de uma lista encadeada.

                De forma bem simples, tem-se o tipo de dado SLINK utilizado pela lista encadeada, que vem a ser no nó da lista, portanto a tabela que precisa ser criada será um vetor deste tipo.


#define TABLE_PRIME_SIZE     1999  // numero primo

SLINK m_ptrTable[TABLE_PRIME_SIZE];


                Com a estrutura de dados que gerencia a lista encadeada precisando de uma pequena alteração para ser utilizada, no exemplo a seguir temos:

SLIST *L1;
char strWord[64];
WORD wIndex;

    // configura a estrutura de dados da lista encadeada
    L1 = CreateLList(   CreateData,
                        DeleteData,
                        DuplicatedNode,
                        NodeDataCmp);

      // calcular a codificacaoh hash
      wIndex = HashDiv(HashPjw("Tabela"));

      // insere na tabela
      L1->slkHead = m_ptrTable[wIndex];

      AddNodeAscend(L1, "Tabela");

      m_ptrTable[wIndex] = L1->slkHead;


                Com um mínimo de código adicionado, modificou-se o exemplo de lista encadeada para uma tabela de dispersão.

Isto se deve em parte pela alteração do endereço de inicio de lista, quando se cria uma lista encadeada a variável L1->slkHead é inicializada através da função AddNodeAscend sempre que está encontra um valor nulo.

Portanto cada vez que apontamos para uma posição com valor nulo na tabela com L1->slkHead = m_ptrTable[wIndex] indica-se uma lista vazia para ser inicializada com AddNodeAscend.

TABLE



. . .


HEAD
NULL
SLINK[16]
NULL



SLINK[17]
NULL



SLINK[18]
NULL



. . .





A rotina AddNodeAscend verifica se a variável de inicio de lista L1->slkHead foi inicializada, sendo nula irá criar um nó e passar o endereço deste, portanto após encontrar um endereço vazio cria o nó.

TABLE



. . .


HEAD
0x1200
SLINK[16]
NULL



SLINK[17]
NULL



SLINK[18]
NULL



. . .





                Com L1->slkHead apontando para um nó valido, atualiza a tabela com o endereço de inicio de lista com m_ptrTable[wIndex] = L1->slkHead.

TABLE



. . .


HEAD
0x1200
SLINK[16]
NULL



SLINK[17]
0x1200



SLINK[18]
NULL



. . .




               
                Conforme ocorre o preenchimento da tabela, cada posição irá apontar para um inicio de lista, sempre que a rotina encontrar um link valido irá percorrer até a posição desejada e inserir o novo dado, como pode acontecer de ser um novo inicio de lista, atualiza o endereço da tabela com o de inicio.
               
                Declarar uma global e utilizar no programa é uma péssima pratica de programação, permitindo a situação de a variável ser modificada em qualquer local do programa, que acaba ficando dependente da mesma, esse potencial de criar dependências mutuas é ilimitado e acaba gerando uma complexidade crescente conforme o código aumenta.

                Para efeitos de explicação utilizamos uma tabela declarada de tal maneira devido à simplicidade, entretanto não se deve utilizar esta solução na pratica, para corrigir este aspecto, segue abaixo as rotinas da tabela de dispersão que complementam a de lista encadeada genérica.

                O arquivo de cabeçalho hashtab.h irá incluir o TAD (tipo abstrato de dado) tabela de dispersão como um superconjunto da TAD lista encadeada, conceito conhecido como herança na programação OO (Orientada a Objetos) ao qual uma linguagem estruturada como C não oferece suporte.

// uma estrutura de dados de uma tabela de dispersaoh encadeada
typedef struct tagSHASHTABLE
{
      SLIST *ptrList;   // lista encadeada
      SLINK *ptrTable;  // tabela de dispersão
      int   iSize;      // tamanho da tabela
      int   ( * fDataLen )( void * ); // tamanho do dado
} SHASHTABLE;

                Alem da estrutura de gerenciamento da lista, temos a tabela, o tamanho da mesma e a rotina especifica da aplicação que retorna o tamanho do dado, essa rotina é necessária para calcular o espalhamento na tabela, durante a conversão do dado em chave, é necessário saber o quanto da informação deve ser codificado.

                Portanto alem das rotinas de criar, apagar, comparar e tratar duplicado que fazem parte do arquivo llapp.c, teremos a de tamanho de dado.

int   DataLen(void *data)
{
    return ( (int) strlen( data ));
}
               

As rotinas abaixo fazem parte das primitivas genéricas para tabela de dispersão encadeada, tais funções gerenciam somente os acessos à tabela, sendo completamente independentes da aplicação e não devem ser alteradas.

Função
InitHashTable
Propósito
Cria e inicia uma estrutura de gerenciamento para tabela de dispersão
Parâmetros
ponteiro para as funções especifica da lista
void *(*fCreateData)(void *)
cria dado
int   (*fDeleteData)(void *)
apaga dado
int   (*fDuplicatedNode)(SLINK, SLINK)
tratar dado duplicado.
int   (*fNodeDataCmp)(void *, void *) 
compara nós
Int iTableSize
tamanho da tabela
ponteiro para as funções especifica da tabela
int          (* fDataLen)(void *)
tamanho do dado
Retorno
 ponteiro SHASHTABLE
   pHT -> estrutura de dados que gerencia tabela de dispersão
    NULL - não conseguiu criar a tabela
Descrição
Cria uma estrutura de tabela de dispersão encadeada no tamanho indicado e retorna um ponteiro da mesma. Em erro, retorna NULL. Esta função aceita ponteiros para as cinco funções especificas e inicializa a estrutura com elas.

SHASHTABLE *InitHashTable(  
                  void *( * fCreateData )       ( void * ),
                  BOOL  ( * fDeleteData )       ( void * ),
                  int   ( * fDuplicatedNode )   ( SLINK, SLINK ),
                  int   ( * fNodeDataCmp )      ( void *, void *),
                  int   iTableSize,
                  int   ( * fDataLen )          ( void * ) )
{
      SHASHTABLE *pHT;

      // cria a estrutura de gerenciamento da tabela
      pHT = (SHASHTABLE *) malloc(sizeof(SHASHTABLE));

      // reserva a memoria da tabela
      pHT->ptrTable = calloc(iTableSize, sizeof(SLINK));

      // configura a estrutura de dados da lista encadeada
      pHT->ptrList = CreateLList(  fCreateData,
                                               fDeleteData,
                                               fDuplicatedNode,
                                               fNodeDataCmp);
      // dados para espalhamento
      pHT->iSize = iTableSize;
      pHT->fDataLen = fDataLen;         

      return (pHT);     // devolve o enderecoh
}


Função
AddDataToTable
Propósito
Adiciona um nó em uma lista encadeada na tabela
Parâmetros
 SHASHTABLE *pHT -> ponteiro para estrutura de dados que trata uma tabela
 void *pData -> dado a ser inserido na tabela
Retorno
 TRUE  - adicionou na tabela um elemento
FALSE - não conseguiu inserir na tabela
Descrição
Adiciona um nó dentro de uma tabela de dispersão encadeada.  Como o tipo de dado possui um gerenciador de lista herdado, é preciso adaptar a lista em cima da tabela, portanto cada posição da tabela se refere a um inicio de lista, para que as rotinas de lista tratem corretamente, atualiza a variável inicio de lista para o apontamento da posição da tabela e após o valor ser adicionado, atualizamos o apontamento na tabela caso este tenha sido alterado.

BOOL AddDataToTable(SHASHTABLE *pHT, void *pData)
{
      WORD wIndex;

      // calcular a codificacaoh hash
      wIndex = HashData( pHT, pData );

      // insere na tabela
      pHT->ptrList->slkHead = pHT->ptrTable[wIndex];

      AddNodeAscend(pHT->ptrList, pData);

      // atualiza a tabela, caso a informacaoh
      // tenha sido inserida no inicio
      pHT->ptrTable[wIndex] = pHT->ptrList->slkHead;

      return TRUE;
}


Função
FindNodeAscend
Propósito
Pesquisa na tabela de dispersão encadeada.
Parâmetros
SHASHTABLE *pHT -> estrutura de dados que gerencia a tabela
void *pData -> informação a ser comparada
Retorno
slkPCurr - aponta para o nó desejado na tabela
NULL     - não encontrou o nó
Descrição
Encontra um nó, iniciando a busca na lista apontada pela posição calculada na tabela, formata a informação para utilizar a rotina FindNodeAscend

SLINK FindDataInTable(SHASHTABLE *pHT, void *pData)
{
      WORD wIndex;
      void *pInfo;
      SLINK slk;

      // calcula a codificacaoh hash
      wIndex = HashData( pHT, pData );

      // direciona para tabela
      pHT->ptrList->slkHead = pHT->ptrTable[wIndex];

      // formata o dado para realizar a busca
      pInfo = pHT->ptrList->fCreateData(pData);

      slk = FindNodeAscend(pHT->ptrList, pInfo);

      // descarta o dado formatado
      pHT->ptrList->fDeleteData(pInfo);

      // retorna o enderecoh na lista
      return slk;
}


Função
DelDataInTable
Propósito
Apaga um nó dentro da tabela de dispersão
Parâmetros
SHASHTABLE *pHT -> estrutura com as rotinas de busca e exclusão
void *pData -> informação a ser excluída
Retorno
TRUE  - exclui o dado e atualizou a tabela
FALSE - não era um dado valido p/ exclusa oh
Descrição
Após calcular a posição na tabela, formata o dado para buscar sua posição na lista, caso tenha encontrado a informação desejada, elimina o nó utilizando a função DeleteNode, para a função DeleteNode funcionar corretamente é preciso atualiza as variáveis de gerenciamento da lista de inicio e fim, com os valores apontados pela posição calculada da tabela, ao fim da exclusão, atualiza o valor de apontamento na tabela.

BOOL DelDataInTable(SHASHTABLE *pHT,  void *pData)
{
      WORD wIndex;
      void *pInfo;
      SLINK slk;
      BOOL bOk;

      // calcular a codificacaoh hash
      wIndex = HashData( pHT, pData );

      // direciona para tabela
      pHT->ptrList->slkHead = pHT->ptrTable[wIndex];

      // formata o dado para realizar a busca
      pInfo = pHT->ptrList->fCreateData(pData);

      slk = FindNodeAscend(pHT->ptrList, pInfo);

      // descarta o dado formatado
      pHT->ptrList->fDeleteData(pInfo);

      if (slk)
      {
            // primeiro da lista
            if (slk->prior == NULL)
                  slk = pHT->ptrList->slkHead;

            // ultimo da lista
            if (slk->next == NULL)
                  slk = pHT->ptrList->slkTail;

            // elimina o dado
            bOk = DeleteNode(pHT->ptrList, slk);

            // atualiza tabela
            pHT->ptrTable[wIndex] = pHT->ptrList->slkHead;
      }
      else
            bOk = FALSE;

      return bOk;
}


Função
EndHashTable
Propósito
Apaga a estrutura de gerenciamento da tabela de dispersão e suas listas encadeadas
Parâmetros
SHASHTABLE *pHT -> estrutura de dados que gerencia tabela de dispersão
Retorno
TRUE  - liberou toda a memória utilizada pela tabela
FALSE - não conseguiu apagar a lista encadeada
Descrição
Após verificar que se trata de uma tabela valida, percorre a mesma, apagando os nós individualmente, por fim, libera a memória usada pela própria estrutura de gerenciamento da lista encadeada e da tabela de dispersão.

BOOL EndHashTable(SHASHTABLE *pHT)
{
      int iCnt;
      SLINK pCurr, pBak;

      if (pHT == NULL)
            return FALSE;

      // percorre cada posicaoh da tabela
      for (iCnt = 0; iCnt < pHT->iSize; ++iCnt)
      {
            pCurr = pHT->ptrList->slkHead = pHT->ptrTable[iCnt];

            if (pCurr != NULL)
            {
                  // percorre a lista apagando todos os nohs
                  while ( pCurr != NULL )
                  {
                        pBak = pCurr;
                        pCurr = pCurr->next;
                        if (pCurr == NULL)
                             pHT->ptrList->slkTail = pBak;
                        DeleteNode(pHT->ptrList, pBak);
                  }
            }
      }

      // libera a estrutura que trata a lista encadeada
      free(pHT->ptrList); 
      // libera a estrutura que trata a tabela
      free(pHT);             

      return TRUE;
}

                Para o calculo de índice na tabela, foi realizada uma alteração no modelo apresentado na postagem de espalhamento, se utiliza a rotina especifica que retorna o tamanho da informação que será processada para adquirir a chave. Caso a tabela definida seja par, escolhe uma função diferente para a transformação da chave em índice.

PRIVATE DWORD HashElf ( const BYTE *ptrData, int iLen )
{
      DWORD dwKey = 0, dwTmp;

      for ( ; iLen > 0; --iLen )
      {
            dwKey = ( dwKey << 4 ) + *ptrData++;
            if ( dwTmp = dwKey & 0xF0000000 )
                  dwKey ^= dwTmp >> 24;
            dwKey &= ~dwTmp;
      }

      return dwKey;
}

WORD HashData(SHASHTABLE *pHT, void *pData)
{
      DWORD dwKey;
      WORD wIndex;
      WORD ( * fHash ) (DWORD, int);

      // trasnforma os dados em uma chave
      // parte especifica do codigo dependente dos dados escolhidos
      dwKey = HashElf(pData, pHT->fDataLen(pData));

      // se tamanho da tabela for potencia de 2
      fHash = (pHT->iSize & 1) ? HashDiv : HashMul;

      // calcular o indice
      wIndex = fHash( dwKey , pHT->iSize );

      return wIndex;
}

                Por fim, no arquivo main.c teremos a aplicação que demonstra como criar e inicializar uma tabela de dispersão e inserir as palavras de um texto na tabela.

#define TABLE_PRIME_SIZE     997   // numero primo

int main(int argc, char *argv[])
{
      FILE *fin;
      char strWord[64];
      char *ptrStr;
      int iChar;
      SHASHTABLE *sHT;


      fin = fopen(argv[1], "rt");

      // cria uma estrutura de dados de dispersao encadeada
      sHT = InitHashTable(   CreateData,
                             DeleteData,
                             DuplicatedNode,
                             NodeDataCmp,
                             TABLE_PRIME_SIZE,
                             DataLen);

      // comecah a processar o arquivo
      while (!feof(fin))
      {
            // percorre o texto pulando os espacosh e pontuncaoesh
            do {
                  iChar = getc(fin);
            } while (   iChar != EOF &&
                        (isspace(iChar) || ispunct(iChar)));

            // tendo um caracter valido
            // comecah a montar a palavra
            ptrStr = strWord;
            do {
                  *ptrStr++ = iChar;
                  iChar = getc(fin);
            } while (   iChar != EOF &&
                        !isspace(iChar) &&
                        !ispunct(iChar) );

            // fim do arquivo, sai do loop
            if (iChar == EOF)
                  break;

            // fecha a palavra com o terminador nulo
            *ptrStr = '\0';

            // todos os caracteres em maiuscula
            ptrStr = strupr(strWord);

            // insere na tabela
            AddDataToTable(sHT, ptrStr);
      }     // while (!feof(fin))

      fclose(fin);

      // mostra as estatistica de uso da tabela
      ListTableStat(sHT);

      // encerra a
      EndHashTable(sHT);

      return (EXIT_SUCCESS);
}

Ao final chama a rotina ListTableStat que mostra as estatísticas de uso da tabela.

C:\Projects\ExternalChaining>ExternalChaining 244.txt
 16 listas encadeadas com  1 nohs
 51 listas encadeadas com  2 nohs
102 listas encadeadas com  3 nohs
148 listas encadeadas com  4 nohs
176 listas encadeadas com  5 nohs
160 listas encadeadas com  6 nohs
125 listas encadeadas com  7 nohs
 87 listas encadeadas com  8 nohs
 62 listas encadeadas com  9 nohs
 36 listas encadeadas com 10 nohs
 21 listas encadeadas com 11 nohs
  6 listas encadeadas com 12 nohs
  3 listas encadeadas com 13 nohs

5687 Nohs em 993 listas encadeadas
Tamanho da tabela de dispersão = 997
Tamanho medio das listas = 5.727090
Recipientes ocupados = 0.995988
Coeficiente de carregamento = 5.704112

Como sugestão para o teste acesse o Projeto Gutenberg, que é um esforço voluntário para arquivar e distribuir obras culturais através da digitalização de livros. Nele é possível encontrar obras que estão em domínio publico em vários formatos como EPUB, HTML e texto, no teste realizado foi utilizada a obra de Arthur Conan Doyle, Um Estudo em Vermelho cujo texto original em inglês pode ser encontrado neste LINK.

Para evitar o excesso de informação, algumas funções apresentadas não estão com o a verificação de erro incluída, os arquivos do projeto que se encontram neste LINK vem com todas as checagens de problemas.