Szybki sposób na zaimplementowanie słownika w C

136

Jedną z rzeczy, których brakuje mi podczas pisania programów w C, jest struktura danych słownikowych. Jaki jest najwygodniejszy sposób zaimplementowania go w C? Nie szukam wydajności, ale łatwości kodowania od podstaw. Nie chcę też, żeby był ogólny - wystarczy coś takiego jak string-> int. Ale chcę, aby można było przechowywać dowolną liczbę elementów.

Ma to bardziej służyć jako ćwiczenie. Wiem, że są dostępne biblioteki innych firm, z których można korzystać. Ale pomyśl przez chwilę, że nie istnieją. W takiej sytuacji najszybciej można zaimplementować słownik spełniający powyższe wymagania.

Rohit
źródło
4
Jeśli tęsknisz za dostarczeniem go dla siebie, dlaczego chcesz zrobić to od zera, zamiast korzystać z implementacji innej firmy?
Karl Knechtel
Tak, ta alternatywa zawsze istnieje. Zadałem to pytanie bardziej jako ćwiczenie.
Rohit
11
Pisanie tablicy hashy w C to fajne ćwiczenie - każdy poważny programista C powinien zrobić to przynajmniej raz.
Lee
Myślę, że słownik jest raczej typem danych niż strukturą danych, ponieważ można go zaimplementować na wiele sposobów - listę, tablicę haszującą, drzewo, samo równoważące się drzewo itp. Czy pytasz o słownik lub tablicę haszującą ?
Paul Hankin,
1
Powiązane: Jak do reprezentowania Python podobny słownika w C [] (? Stackoverflow.com/questions/3269881/... )
Gaurang Tandon

Odpowiedzi:

118

Sekcja 6.6 języka programowania C przedstawia prostą słownikową (z możliwością hashy) strukturę danych. Nie sądzę, żeby użyteczna implementacja słownika mogła być prostsza niż ta. Dla Twojej wygody reprodukuję tutaj kod.

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

Zwróć uwagę, że jeśli zderzają się skróty dwóch ciągów, może to prowadzić do O(n)czasu wyszukiwania. Możesz zmniejszyć prawdopodobieństwo kolizji, zwiększając wartość HASHSIZE. Pełne omówienie struktury danych można znaleźć w książce.

Vijay Mathew
źródło
31
@Rohit, jeśli chodzi o fragment przydatnego kodu w C, nie jest on dużo bardziej zwarty. Przypuszczam, że zawsze możesz usunąć jakąś białą przestrzeń ...
Ryan Calhoun,
8
dlaczego tutaj jest hashval = *s + 31 * hashval;dokładnie 31 i nic więcej?
ア レ ッ ク ス
14
31 jest liczbą pierwszą. Liczby pierwsze są często używane w funkcjach skrótu, aby zmniejszyć prawdopodobieństwo kolizji. Ma to coś wspólnego z rozkładaniem na czynniki całkowite (tj. Nie można rozkładać na czynniki pierwsze).
jnovacho
2
Zauważ, że algorytm haszujący K&R C jest przerażającym algorytmem haszującym. Zobacz: programmers.stackexchange.com/questions/49550/…, aby uzyskać szczegółowe informacje na temat tego, jak naprawdę straszne jest to. Przestań go używać !!
carveone
2
@Overdrivr: Nie jest to konieczne w tym przypadku. hashtab ma statyczny czas trwania. Niezainicjowane zmienne ze statycznym czasem trwania (to znaczy te zadeklarowane poza funkcjami i te zadeklarowane za pomocą statycznej klasy pamięci) są gwarantowane jako zero odpowiedniego typu (tj .: 0 lub NULL lub 0,0)
carveone
20

Najszybszym sposobem byłoby użyć już istniejących implementacji, jak uthash .

A jeśli naprawdę chcesz to samemu zakodować, algorytmy z programu uthashmogą zostać sprawdzone i ponownie użyte. Jest na licencji BSD, więc oprócz wymogu przekazania informacji o prawach autorskich, masz prawie nieograniczone możliwości z nim związane.

paxdiablo
źródło
8

Ze względu na łatwość implementacji trudno pokonać naiwne przeszukiwanie tablicy. Oprócz sprawdzania błędów jest to pełna implementacja (nieprzetestowana).

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
źródło
2
„Dla ułatwienia wdrożenia”: Masz rację: to jest najłatwiejsze. Dodatkowo realizuje żądanie OP „Chcę, aby był w stanie przechowywać dowolną liczbę elementów” - najwyższa głosowana odpowiedź tego nie robi (chyba że uważasz, że wybranie stałej czasu kompilacji spełnia "arbitralność" ...)
davidbak
2
Może to być poprawne podejście w zależności od przypadku użycia, ale OP wyraźnie zażądał słownika, a to zdecydowanie nie jest słownik.
Dan Bechard,
3

Utwórz prostą funkcję skrótu i ​​kilka połączonych list struktur, w zależności od skrótu, przypisz, do której listy połączonej chcesz wstawić wartość. Użyj skrótu do jego odzyskania.

Jakiś czas temu wykonałem prostą implementację:

...
# zdefiniować K 16 // współczynnik łańcuchowy

struct dict
{
    char * name; / * nazwa klucza * /
    int val; / * wartość * /
    struct dict * next; / * pole linku * /
};

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


void putval (char *, int);

void init_dict ()
{   
    zainicjalizowane = 1;
    int i;  
    for (i = 0; iname = (char *) malloc (strlen (key_name) +1);
    ptr-> val = sval;
    strcpy (ptr-> nazwa, nazwa_klucza);


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

}


int getval (char * key_name)
{   
    int hsh = hash (nazwa_klucza);   
    dict * ptr;
    for (ptr = table [hsh]; ptr! = (dict *) 0;
        ptr = (dict *) ptr-> next)
    if (strcmp (ptr-> name, key_name) == 0)
        return ptr-> val;
    return -1;
}
abc def foo bar
źródło
1
Nie brakuje ci połowy kodu? gdzie jest „hash ()” i „putval ()”?
swdev
3

GLib i gnulib

Są to prawdopodobnie najlepsze zakłady, jeśli nie masz bardziej szczegółowych wymagań, ponieważ są one powszechnie dostępne, przenośne i prawdopodobnie wydajne.

Zobacz też: Czy istnieją biblioteki C typu open source ze wspólnymi strukturami danych?

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
źródło
2

tutaj jest szybkie narzędzie, użyłem go do uzyskania „Matrixa” (wyciągu) z łańcucha. możesz mieć większą tablicę i zmieniać jej wartości w biegu również:

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
źródło
2

Dziwię się, że nikt nie wspomniał o zestawie bibliotek hsearch / hcreate, który chociaż nie jest dostępny w systemie Windows, ale jest wymagany przez POSIX, a zatem jest dostępny w systemach Linux / GNU.

Link zawiera prosty i kompletny podstawowy przykład, który bardzo dobrze wyjaśnia jego użycie.

Ma nawet wariant bezpieczny dla gwintów, jest łatwy w użyciu i bardzo wydajny.

fkl
źródło
2
Warto zauważyć, że ludzie tutaj mówią, że jest to trochę bezużyteczne, chociaż sam tego nie próbowałem: stackoverflow.com/a/6118591/895245
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
1
W porządku, jednak wypróbowałem wersję hcreate_r (dla wielu tabel haszujących) w co najmniej jednej aplikacji, która działała wystarczająco długo, aby uznać ją za prawdziwy świat. Zgodziłem się, że jest to rozszerzenie GNU, ale tak jest też w przypadku wielu innych bibliotek. Chociaż nadal argumentowałbym, że nadal możesz go używać do jednej dużej pary klucza i wartości obsługiwanej w jakiejś rzeczywistej aplikacji
fkl
0

Hashtable to tradycyjna implementacja prostego „Słownika”. Jeśli nie zależy Ci na szybkości lub rozmiarze, po prostu wygoogluj to . Istnieje wiele bezpłatnych wdrożeń.

oto pierwszy, który widziałem - na pierwszy rzut oka wygląda dobrze. (jest to dość proste. Jeśli naprawdę chcesz, aby zawierała nieograniczoną ilość danych, musisz dodać trochę logiki, aby „ponownie przydzielić” pamięć tabeli w miarę jej wzrostu).

powodzenia!

Zawietrzny
źródło
-1

Kluczem jest haszowanie. Myślę, że użyj do tego tabeli odnośników i klucza haszującego. W Internecie można znaleźć wiele funkcji mieszania.

ashmish2
źródło
-2

Najszybszą metodą byłoby użycie drzewa binarnego. Jego najgorszym przypadkiem jest też tylko O ​​(logn).

cprogrammer
źródło
15
To jest niepoprawne. Najgorszy przypadek wyszukiwania dla drzewa binarnego to O (n) (zdegenerowany przypadek z powodu złej kolejności wstawiania, co w zasadzie prowadzi do listy linków), gdy jest niezrównoważone.
Randy Howard,