Manera rápida de implementar el diccionario en C

132

Una de las cosas que echo de menos al escribir programas en C es una estructura de datos del diccionario. ¿Cuál es la forma más conveniente de implementar uno en C? No busco rendimiento, sino facilidad para codificarlo desde cero. Tampoco quiero que sea genérico, algo como string-> int servirá. Pero sí quiero que pueda almacenar una cantidad arbitraria de artículos.

Esto se pretende más como un ejercicio. Sé que hay bibliotecas de terceros disponibles que se pueden usar. Pero considere por un momento, que no existen. En tal situación, ¿cuál es la forma más rápida de implementar un diccionario que satisfaga los requisitos anteriores?

Rohit
fuente
44
Si echa de menos que se lo proporcionen, ¿por qué desea hacerlo desde cero, en lugar de utilizar una implementación de terceros?
Karl Knechtel
Sí, esa alternativa siempre existe. Planteé esta pregunta más como un ejercicio.
Rohit
10
Escribir una tabla hash en C es un ejercicio divertido: todo programador serio de C debe hacerlo al menos una vez.
Lee
Pienso en un diccionario que es un tipo de datos en lugar de una estructura de datos, ya que podría implementarse de muchas maneras: una lista, una tabla hash, un árbol, un árbol de equilibrio automático, etc. ¿Está solicitando un diccionario o una tabla hash? ?
Paul Hankin
1
Relacionado: ¿Cómo representar un diccionario similar a Python en C? [] ( Stackoverflow.com/questions/3269881/… )
Gaurang Tandon

Respuestas:

114

La sección 6.6 del lenguaje de programación C presenta una estructura de datos de diccionario simple (tabla hash). No creo que una implementación útil del diccionario pueda ser más simple que esto. Para su comodidad, reproduzco el código aquí.

struct nlist { /* table entry: */
    struct nlist *next; /* next entry in chain */
    char *name; /* defined name */
    char *defn; /* replacement text */
};

#define HASHSIZE 101
static struct nlist *hashtab[HASHSIZE]; /* pointer table */

/* hash: form hash value for string s */
unsigned hash(char *s)
{
    unsigned hashval;
    for (hashval = 0; *s != '\0'; s++)
      hashval = *s + 31 * hashval;
    return hashval % HASHSIZE;
}

/* lookup: look for s in hashtab */
struct nlist *lookup(char *s)
{
    struct nlist *np;
    for (np = hashtab[hash(s)]; np != NULL; np = np->next)
        if (strcmp(s, np->name) == 0)
          return np; /* found */
    return NULL; /* not found */
}

char *strdup(char *);
/* install: put (name, defn) in hashtab */
struct nlist *install(char *name, char *defn)
{
    struct nlist *np;
    unsigned hashval;
    if ((np = lookup(name)) == NULL) { /* not found */
        np = (struct nlist *) malloc(sizeof(*np));
        if (np == NULL || (np->name = strdup(name)) == NULL)
          return NULL;
        hashval = hash(name);
        np->next = hashtab[hashval];
        hashtab[hashval] = np;
    } else /* already there */
        free((void *) np->defn); /*free previous defn */
    if ((np->defn = strdup(defn)) == NULL)
       return NULL;
    return np;
}

char *strdup(char *s) /* make a duplicate of s */
{
    char *p;
    p = (char *) malloc(strlen(s)+1); /* +1 for ’\0’ */
    if (p != NULL)
       strcpy(p, s);
    return p;
}

Tenga en cuenta que si los hashes de dos cadenas chocan, puede provocar un O(n)tiempo de búsqueda. Puede reducir la probabilidad de colisiones aumentando el valor de HASHSIZE. Para una discusión completa de la estructura de datos, consulte el libro.

Vijay Mathew
fuente
1
Si es del libro C, me pregunto si puede haber una implementación más compacta.
Rohit
30
@Rohit, para una pieza de código C útil, no se vuelve mucho más compacto que eso. Supongo que siempre puedes eliminar algunos espacios en blanco ...
Ryan Calhoun
77
¿Por qué está aquí hashval = *s + 31 * hashval;exactamente 31 y no otra cosa?
ア レ ッ ク ス
12
31 es primo. Los primos se usan a menudo en funciones hash para reducir la probabilidad de colisiones. Tiene algo que ver con la factorización de enteros (es decir, no se puede factorizar un primo).
jnovacho
2
@ Overdrivr: No es necesario en esta instancia. hashtab es de duración estática. Se garantiza que las variables no inicializadas con duración estática (es decir, aquellas declaradas fuera de las funciones y aquellas declaradas con la clase de almacenamiento estática) comienzan como cero del tipo correcto (es decir: 0 o NULL o 0.0)
carveone
19

La forma más rápida sería utilizar una implementación ya existente, como uthash .

Y, si realmente desea codificarlo usted mismo, los algoritmos de uthashpueden ser examinados y reutilizados. Tiene licencia BSD, por lo que, aparte del requisito de transmitir el aviso de derechos de autor, tiene bastante ilimitado en lo que puede hacer con él.

paxdiablo
fuente
8

Para facilitar la implementación, es difícil superar la búsqueda ingenua a través de una matriz. Además de alguna comprobación de errores, esta es una implementación completa (no probada).

typedef struct dict_entry_s {
    const char *key;
    int value;
} dict_entry_s;

typedef struct dict_s {
    int len;
    int cap;
    dict_entry_s *entry;
} dict_s, *dict_t;

int dict_find_index(dict_t dict, const char *key) {
    for (int i = 0; i < dict->len; i++) {
        if (!strcmp(dict->entry[i], key)) {
            return i;
        }
    }
    return -1;
}

int dict_find(dict_t dict, const char *key, int def) {
    int idx = dict_find_index(dict, key);
    return idx == -1 ? def : dict->entry[idx].value;
}

void dict_add(dict_t dict, const char *key, int value) {
   int idx = dict_find_index(dict, key);
   if (idx != -1) {
       dict->entry[idx].value = value;
       return;
   }
   if (dict->len == dict->cap) {
       dict->cap *= 2;
       dict->entry = realloc(dict->entry, dict->cap * sizeof(dict_entry_s));
   }
   dict->entry[dict->len].key = strdup(key);
   dict->entry[dict->len].value = value;
   dict->len++;
}

dict_t dict_new(void) {
    dict_s proto = {0, 10, malloc(10 * sizeof(dict_entry_s))};
    dict_t d = malloc(sizeof(dict_s));
    *d = proto;
    return d;
}

void dict_free(dict_t dict) {
    for (int i = 0; i < dict->len; i++) {
        free(dict->entry[i].key);
    }
    free(dict->entry);
    free(dict);
}
Paul Hankin
fuente
2
"Para facilitar la implementación": Tiene toda la razón: esta es la más fácil. Además, implementa la solicitud del OP "Quiero que sea capaz de almacenar un número arbitrario de elementos": la respuesta más votada no lo hace (a menos que crea que elegir una constante de tiempo de compilación satisface "arbitrario" ...)
davidbak
1
Este puede ser un enfoque válido según el caso de uso, pero el OP solicitó explícitamente un diccionario, y definitivamente no es un diccionario.
Dan Bechard el
3

Cree una función hash simple y algunas listas de estructuras vinculadas, dependiendo del hash, asigne en qué lista vinculada insertar el valor. Use el hash para recuperarlo también.

Hice una implementación simple hace algún tiempo:

...
#define K 16 // coeficiente de encadenamiento

struct dict
{
    nombre del personaje; / * nombre de la clave * /
    int val; / * valor * /
    struct dict * next; / * campo de enlace * /
};

typedef struct dict dict;
dict * tabla [K];
int inicializado = 0;


nulo putval (char *, int);

void init_dict ()
{   
    inicializado = 1;
    int i;  
    para (i = 0; iname = (char *) malloc (strlen (key_name) +1);
    ptr-> val = sval;
    strcpy (ptr-> nombre, nombre_clave);


    ptr-> next = (struct dict *) tabla [hsh];
    tabla [hsh] = ptr;

}


int getval (char * key_name)
{   
    int hsh = hash (nombre_clave);   
    dict * ptr;
    para (ptr = tabla [hsh]; ptr! = (dict *) 0;
        ptr = (dict *) ptr-> siguiente)
    if (strcmp (ptr-> name, key_name) == 0)
        volver ptr-> val;
    volver -1;
}
abc def foo bar
fuente
1
¿No te falta la mitad del código? ¿Dónde está "hash ()" y "putval ()"?
swdev
3

GLib y gnulib

Estas son sus mejores apuestas si no tiene requisitos más específicos, ya que están ampliamente disponibles, son portátiles y probablemente eficientes.

Ver también: ¿Hay alguna biblioteca C de código abierto con estructuras de datos comunes?

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
fuente
2

Aquí hay un implemento rápido, lo usé para obtener una 'Matriz' (sruct) de una cadena. puede tener una matriz más grande y cambiar sus valores en la ejecución también:

typedef struct  { int** lines; int isDefined; }mat;
mat matA, matB, matC, matD, matE, matF;

/* an auxilary struct to be used in a dictionary */
typedef struct  { char* str; mat *matrix; }stringToMat;

/* creating a 'dictionary' for a mat name to its mat. lower case only! */
stringToMat matCases [] =
{
    { "mat_a", &matA },
    { "mat_b", &matB },
    { "mat_c", &matC },
    { "mat_d", &matD },
    { "mat_e", &matE },
    { "mat_f", &matF },
};

mat* getMat(char * str)
{
    stringToMat* pCase;
    mat * selected = NULL;
    if (str != NULL)
    {
        /* runing on the dictionary to get the mat selected */
        for(pCase = matCases; pCase != matCases + sizeof(matCases) / sizeof(matCases[0]); pCase++ )
        {
            if(!strcmp( pCase->str, str))
                selected = (pCase->matrix);
        }
        if (selected == NULL)
            printf("%s is not a valid matrix name\n", str);
    }
    else
        printf("expected matrix name, got NULL\n");
    return selected;
}
Dagoltz
fuente
2

Me sorprende que nadie haya mencionado el conjunto de bibliotecas hsearch / hcreate que, aunque no está disponible en Windows, es un mandato de POSIX y, por lo tanto, está disponible en sistemas Linux / GNU.

El enlace tiene un ejemplo básico simple y completo que explica muy bien su uso.

Incluso tiene una variante segura para hilos, es fácil de usar y muy eficiente.

fkl
fuente
2
Vale la pena señalar que la gente aquí dice que es inutilizable, aunque no lo he intentado yo mismo: stackoverflow.com/a/6118591/895245
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
1
Sin embargo, he probado la versión hcreate_r (para varias tablas hash) en al menos una aplicación que se ejecutó durante un tiempo razonablemente suficiente para considerarla en el mundo real. Estuvo de acuerdo en que es una extensión GNU, pero ese es el caso de muchas otras librerías también. Aunque todavía argumentaría que aún podría usarlo para un par de valores clave grandes que se opera en alguna aplicación del mundo real
fkl
0

Una tabla hash es la implementación tradicional de un simple "Diccionario". Si no te importa la velocidad o el tamaño, solo búscalo en google . Hay muchas implementaciones disponibles gratuitamente.

Aquí está el primero que vi : de un vistazo, me parece bien. (es bastante básico. Si realmente desea que contenga una cantidad ilimitada de datos, deberá agregar algo de lógica para "reasignar" la memoria de la tabla a medida que crece).

¡buena suerte!

Sotavento
fuente
-1

Hashing es la clave. Creo que use la tabla de búsqueda y la clave hash para esto. Puede encontrar muchas funciones de hashing en línea.

ashmish2
fuente
-1

El método más rápido sería usar un árbol binario. Su peor caso también es solo O (logn).

programador
fuente
15
Esto es incorrecto. La búsqueda del peor caso para un árbol binario es O (n) (caso degenerado debido a un mal orden de inserción, lo que resulta en una lista de enlaces, básicamente) cuando está desequilibrado.
Randy Howard