Moyen rapide d'implémenter le dictionnaire en C

132

Une des choses qui me manque lors de l'écriture de programmes en C est une structure de données de dictionnaire. Quel est le moyen le plus pratique d'en implémenter un en C? Je ne recherche pas la performance, mais la facilité de le coder à partir de zéro. Je ne veux pas non plus que ce soit générique - quelque chose comme string-> int fera l'affaire. Mais je veux qu'il puisse stocker un nombre arbitraire d'articles.

Il s'agit plutôt d'un exercice. Je sais qu'il existe des bibliothèques tierces disponibles que l'on peut utiliser. Mais considérez un instant qu'ils n'existent pas. Dans une telle situation, quel est le moyen le plus rapide d'implémenter un dictionnaire répondant aux exigences ci-dessus.

Rohit
la source
4
Si vous manquez de l'avoir fourni pour vous, alors pourquoi voulez-vous le créer à partir de zéro, au lieu d'utiliser une implémentation tierce?
Karl Knechtel
Oui, cette alternative existe toujours. J'ai posé cette question davantage comme un exercice.
Rohit
10
Ecrire une table de hachage en C est un exercice amusant - tout programmeur C sérieux devrait le faire au moins une fois.
Lee
Je pense qu'un dictionnaire est un type de données plutôt qu'une structure de données, car il pourrait être implémenté de nombreuses façons - une liste, une table de hachage, un arbre, un arbre à équilibrage automatique, etc. Demandez-vous un dictionnaire, ou une table de hachage ?
Paul Hankin
1
En relation: Comment représenter un dictionnaire de type Python en C? [] ( Stackoverflow.com/questions/3269881/… )
Gaurang Tandon

Réponses:

114

La section 6.6 du langage de programmation C présente une structure de données de dictionnaire simple (table de hachage). Je ne pense pas qu'une implémentation de dictionnaire utile puisse être plus simple que cela. Pour votre commodité, je reproduis le code ici.

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

Notez que si les hachages de deux chaînes entrent en collision, cela peut entraîner un O(n)temps de recherche. Vous pouvez réduire la probabilité de collisions en augmentant la valeur de HASHSIZE. Pour une discussion complète de la structure des données, veuillez consulter le livre.

Vijay Mathew
la source
1
Si c'est du livre C, je me demande s'il peut y avoir une implémentation plus compacte.
Rohit
30
@Rohit, pour un morceau de code C utile, il n'est pas beaucoup plus compact que ça. Je suppose que vous pouvez toujours supprimer des espaces ...
Ryan Calhoun
7
pourquoi est ici hashval = *s + 31 * hashval;exactement 31 et rien d'autre?
ア レ ッ ク ス
12
31 est le premier. Les primes sont souvent utilisées dans les fonctions de hachage pour réduire la probabilité de collisions. Cela a quelque chose à voir avec la factorisation entière (c'est-à-dire que vous ne pouvez pas factoriser un nombre premier).
jnovacho
2
@Overdrivr: Pas nécessaire dans ce cas. hashtab est de durée statique. Les variables non initialisées avec une durée statique (c'est-à-dire celles déclarées en dehors des fonctions et celles déclarées avec la classe de stockage static), sont garanties de commencer par un zéro du bon type (c'est-à-dire: 0 ou NULL ou 0.0)
carveone
19

Le moyen le plus rapide serait d'utiliser une implémentation déjà existante, comme uthash .

Et, si vous voulez vraiment le coder vous-même, les algorithmes de uthashpeuvent être examinés et réutilisés. Il est sous licence BSD, donc, mis à part l'obligation de transmettre l'avis de droit d'auteur, vous êtes pratiquement illimité dans ce que vous pouvez en faire.

paxdiablo
la source
8

Pour faciliter la mise en œuvre, il est difficile de battre naïvement la recherche dans un tableau. Outre une vérification des erreurs, il s'agit d'une implémentation complète (non testée).

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
la source
2
"Pour faciliter la mise en œuvre": vous avez parfaitement raison: c'est le plus simple. De plus, il implémente la demande du PO "Je veux qu'il puisse stocker un nombre arbitraire d'éléments" - la réponse la plus élevée ne le fait pas (sauf si vous pensez que choisir une constante de temps de compilation satisfait "arbitraire" ...)
davidbak
1
Cela peut être une approche valide selon le cas d'utilisation, mais l'OP a explicitement demandé un dictionnaire, et ce n'est certainement pas un dictionnaire.
Dan Bechard
3

Créez une fonction de hachage simple et des listes de structures liées, en fonction du hachage, attribuez la liste liée dans laquelle insérer la valeur. Utilisez également le hachage pour le récupérer.

J'ai fait une implémentation simple il y a quelque temps:

...
#define K 16 // coefficient de chaînage

struct dict
{
    char * nom; / * nom de la clé * /
    int val; /* valeur */
    struct dict * suivant; / * champ de lien * /
};

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


void putval (char *, int);

void init_dict ()
{   
    initialisé = 1;
    int i;  
    for (i = 0; iname = (char *) malloc (strlen (key_name) +1);
    ptr-> val = sval;
    strcpy (ptr-> nom, nom_clé);


    ptr-> suivant = (struct dict *) table [hsh];
    table [hsh] = ptr;

}


int getval (char * nom_clé)
{   
    int hsh = hash (nom_clé);   
    dict * ptr;
    pour (ptr = table [hsh]; ptr! = (dict *) 0;
        ptr = (dict *) ptr-> suivant)
    if (strcmp (ptr-> nom, nom_clé) == 0)
        return ptr-> val;
    return -1;
}
abc def foo bar
la source
1
Ne manquez-vous pas la moitié du code? où sont "hash ()" et "putval ()"?
swdev
3

GLib et gnulib

Ce sont probablement vos meilleurs paris si vous n'avez pas d'exigences plus spécifiques, car ils sont largement disponibles, portables et probablement efficaces.

Voir aussi: Existe-t-il des bibliothèques C open source avec des structures de données communes?

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

voici un outil rapide, je l'ai utilisé pour obtenir une 'Matrix' (sruct) à partir d'une chaîne. vous pouvez également avoir un tableau plus grand et modifier ses valeurs en cours d'exécution:

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
la source
2

Je suis surpris que personne n'ait mentionné l' ensemble de bibliothèques hsearch / hcreate qui, bien que n'étant pas disponible sur Windows, mais mandaté par POSIX, et donc disponible dans les systèmes Linux / GNU.

Le lien a un exemple de base simple et complet qui explique très bien son utilisation.

Il a même une variante thread-safe, est facile à utiliser et très performant.

fkl
la source
2
Il convient de noter que les gens ici disent que c'est un peu inutilisable, même si je ne l'ai pas essayé moi-même: stackoverflow.com/a/6118591/895245
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
1
Assez juste, cependant, j'ai essayé la version hcreate_r (pour plusieurs tables de hachage) dans au moins une application qui a fonctionné pendant un temps assez long pour la considérer dans le monde réel. J'ai convenu que c'était une extension GNU, mais c'est aussi le cas pour de nombreuses autres bibliothèques. Même si je dirais toujours que vous pourrez peut-être toujours l'utiliser pour une grande paire de valeurs clés exploitée dans une application du monde réel
fkl
0

Une table de hachage est l'implémentation traditionnelle d'un simple «dictionnaire». Si vous ne vous souciez pas de la vitesse ou de la taille, recherchez simplement sur Google . Il existe de nombreuses implémentations disponibles gratuitement.

voici le premier que j'ai vu - en un coup d'œil, cela me semble correct. (c'est assez basique. Si vous voulez vraiment qu'il contienne une quantité illimitée de données, vous devrez ajouter un peu de logique pour "réallouer" la mémoire de la table à mesure qu'elle grandit.)

bonne chance!

Lee
la source
-1

Le hachage est la clé. Je pense utiliser une table de recherche et une clé de hachage pour cela. Vous pouvez trouver de nombreuses fonctions de hachage en ligne.

ashmish2
la source
-1

La méthode la plus rapide serait d'utiliser un arbre binaire. Son pire cas est également seulement O (logn).

programmeur
la source
15
Ceci est une erreur. Le pire cas de recherche pour un arbre binaire est O (n) (cas dégénéré en raison d'un mauvais ordre d'insertion, résultant en une liste de liens, essentiellement) lorsqu'il est déséquilibré.
Randy Howard