Modo rapido per implementare il dizionario in C.


132

Una delle cose che mi mancano durante la scrittura di programmi in C è una struttura di dati del dizionario. Qual è il modo più conveniente per implementarne uno in C? Non sto cercando prestazioni, ma facilità di codifica da zero. Non voglio che sia generico - qualcosa come string-> int lo farà. Ma voglio che sia in grado di memorizzare un numero arbitrario di articoli.

Questo è inteso più come un esercizio. So che ci sono librerie di terze parti disponibili che si possono usare. Ma considera per un momento che non esistono. In una situazione del genere qual è il modo più rapido per implementare un dizionario che soddisfi i requisiti di cui sopra.


4
Se ti manca averlo fornito per te, allora perché vuoi farlo da zero, invece di utilizzare un'implementazione di terze parti?
Karl Knechtel,

Sì, tale alternativa esiste sempre. Ho posto questa domanda più come un esercizio.
Rohit,

10
Scrivere una hashtable in C è un esercizio divertente - ogni programmatore C serio dovrebbe farlo almeno una volta.
Lee,

Penso che un dizionario sia un tipo di dati piuttosto che una struttura di dati, poiché potrebbe essere implementato in molti modi: un elenco, una tabella hash, un albero, un albero auto-bilanciante, ecc. Stai chiedendo un dizionario o una tabella hash ?
Paul Hankin il

1
Correlati: Come rappresentare un Python-like dizionario in C [] (? Stackoverflow.com/questions/3269881/... )
Gaurang Tandon

Risposte:


114

La sezione 6.6 del linguaggio di programmazione C presenta una struttura dati di dizionario semplice (hashtable). Non credo che un'implementazione del dizionario utile potrebbe essere più semplice di così. Per comodità, riproduco il codice qui.

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;
}

Si noti che se gli hash di due stringhe si scontrano, potrebbe portare a un O(n)tempo di ricerca. È possibile ridurre la probabilità di collisioni aumentando il valore di HASHSIZE. Per una discussione completa della struttura dei dati, consultare il libro.


1
Se proviene dal libro C, mi chiedo se ci può essere un'implementazione più compatta.
Rohit,

30
@Rohit, per un utile codice C, non diventa molto più compatto di così. Suppongo che potresti sempre rimuovere alcuni spazi bianchi ...
Ryan Calhoun l'

7
perché qui è hashval = *s + 31 * hashval;esattamente 31 e non altro?
ア レ ッ ク ス

12
31 è primo. I primes sono spesso usati nelle funzioni hash per ridurre la probabilità di collisioni. Ha qualcosa a che fare con la fattorizzazione dei numeri interi (cioè non puoi fattorizzare un numero primo).
jnovacho,

2
@Overdrivr: non necessario in questa istanza. l'hashtab è di durata statica. Le variabili non inizializzate con durata statica (ovvero quelle dichiarate al di fuori delle funzioni e quelle dichiarate con la classe di memoria statica), sono garantite per iniziare come zero del tipo giusto (cioè: 0 o NULL o 0.0)
carveone

19

Il modo più rapido sarebbe quello di utilizzare un'implementazione già esistente, come uthash .

E, se vuoi davvero codificarlo da solo, gli algoritmi di uthashpossono essere esaminati e riutilizzati. Ha la licenza BSD, quindi, oltre all'obbligo di comunicare l'avviso sul copyright, sei praticamente illimitato in ciò che puoi fare con esso.


8

Per facilità di implementazione, è difficile battere la ricerca ingenua in un array. A parte qualche controllo degli errori, questa è un'implementazione completa (non testata).

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);
}

2
"Per facilità di implementazione": hai esattamente ragione: questa è la più semplice. Inoltre implementa la richiesta del PO "Voglio che sia in grado di memorizzare un numero arbitrario di elementi" - la risposta con il voto più alto non lo fa (a meno che non crediate che la selezione di una costante di tempo di compilazione soddisfi "arbitrario" ...)
davidbak,

1
Questo può essere un approccio valido a seconda del caso d'uso, ma l'OP ha esplicitamente richiesto un dizionario e questo non è sicuramente un dizionario.
Dan Bechard,

3

Crea una semplice funzione hash e alcuni elenchi di strutture collegate, a seconda dell'hash, assegnano la lista collegata in cui inserire il valore. Usa l'hash anche per recuperarlo.

Ho fatto una semplice implementazione qualche tempo fa:

...
#define K 16 // coefficiente di concatenamento

Struct Dict
{
    nome del personaggio; / * nome della chiave * /
    int val; / * valore * /
    struct dict * successivo; / * campo link * /
};

typedef struct dict dict;
dict * table [K];
int inizializzato = 0;


void putval (char *, int);

void init_dict ()
{   
    inizializzato = 1;
    int i;  
    per (i = 0; iname = (char *) malloc (strlen (key_name) +1);
    ptr-> val = sval;
    strcpy (ptr-> name, key_name);


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

}


int getval (char * key_name)
{   
    int hsh = hash (key_name);   
    dict * ptr;
    per (ptr = table [hsh]; ptr! = (dict *) 0;
        ptr = (dict *) ptr-> successivo)
    if (strcmp (ptr-> name, key_name) == 0)
        return ptr-> val;
    ritorno -1;
}

1
Non ti manca metà del codice? dov'è "hash ()" e "putval ()"?
swdev,

3

GLib e gnulib

Queste sono probabilmente le tue migliori scommesse se non hai requisiti più specifici, dal momento che sono ampiamente disponibili, portatili e probabilmente efficienti.

Vedi anche: Esistono librerie C open source con strutture dati comuni?


2

ecco un attrezzo rapido, l'ho usato per ottenere un 'Matrix' (sruct) da una stringa. puoi avere un array più grande e modificarne i valori in esecuzione anche:

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;
}

2

Sono sorpreso che nessuno abbia menzionato il set di librerie hsearch / hcreate che, sebbene non sia disponibile su Windows, ma sia obbligatorio da POSIX e quindi disponibile nei sistemi Linux / GNU.

Il collegamento ha un esempio di base semplice e completo che spiega molto bene il suo utilizzo.

Ha anche una variante thread-safe, è facile da usare e molto performante.


2
Da segnalare che la gente qui dice che è una specie di inutilizzabile, anche se non ho provato io stesso: stackoverflow.com/a/6118591/895245
Ciro Santilli郝海东冠状病六四事件法轮功

1
Abbastanza giusto, tuttavia, ho provato la versione hcreate_r (per più tabelle hash) in almeno un'app che ha funzionato per un tempo abbastanza lungo da considerarlo mondo reale. Concordato sul fatto che si tratta di un'estensione GNU, ma poi è così anche per molte altre librerie. Anche se direi ancora che potresti essere ancora in grado di usarlo per una coppia di valori chiave di grandi dimensioni gestita in qualche app del mondo reale
fkl

0

Una tabella hash è l'implementazione tradizionale di un semplice "Dizionario". Se non ti interessa la velocità o le dimensioni, basta cercare su Google . Esistono molte implementazioni disponibili gratuitamente.

ecco il primo che ho visto - a colpo d'occhio, mi sembra ok. (è piuttosto semplice. Se vuoi davvero che contenga una quantità illimitata di dati, dovrai aggiungere un po 'di logica per "riallocare" la memoria della tabella man mano che cresce.)

in bocca al lupo!


-1

L'hashing è la chiave. Penso che utilizzare la tabella di ricerca e la chiave di hashing per questo. Puoi trovare molte funzioni di hashing online.


-1

Il metodo più veloce sarebbe usare l'albero binario. Il suo caso peggiore è anche solo O (logn).


15
Questo non è corretto La peggiore ricerca nel caso di un albero binario è O (n) (caso degenerato a causa di un cattivo ordine di inserzione, risultante in un elenco di collegamenti, in sostanza) quando è sbilanciato.
Randy Howard,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.