Быстрый способ реализовать словарь на C


133

Одна из вещей, которую мне не хватает при написании программ на C, - это структура данных словаря. Какой самый удобный способ реализовать его на C? Я ищу не производительность, а простоту написания кода с нуля. Я тоже не хочу, чтобы он был универсальным - подойдет что-то вроде string-> int. Но я действительно хочу, чтобы он мог хранить произвольное количество элементов.

Это больше задумано как упражнение. Я знаю, что есть сторонние библиотеки, которые можно использовать. Но подумайте на мгновение, что их не существует. В такой ситуации самый быстрый способ реализовать словарь, удовлетворяющий указанным выше требованиям.


4
Если вы скучаете по его предоставлению, то почему вы хотите сделать его с нуля, а не использовать стороннюю реализацию?
Карл Кнехтель

Да, такая альтернатива существует всегда. Я поставил этот вопрос скорее как упражнение.
Rohit

10
Написание хеш-таблицы на C - забавное упражнение - каждый серьезный программист на C должен сделать это хотя бы раз.
Ли

Я думаю о словаре как о типе данных, а не о структуре данных, поскольку он может быть реализован множеством способов - список, хеш-таблица, дерево, самобалансирующееся дерево и т. Д. Вы просите словарь или хеш-таблицу ?
Пол Ханкин

1
Связанный: Как представлять Питон-подобный словарю в C [] (? Stackoverflow.com/questions/3269881/... )
Gaurang Тандон

Ответы:


115

В разделе 6.6 языка программирования C представлена ​​простая структура данных словаря (хеш-таблицы). Я не думаю, что полезная реализация словаря могла бы быть проще, чем эта. Для вашего удобства я воспроизвожу код здесь.

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

Обратите внимание, что если хэши двух строк сталкиваются, это может привести к увеличению O(n)времени поиска. Вы можете уменьшить вероятность столкновений, увеличив значение HASHSIZE. Полное обсуждение структуры данных см. В книге.


1
Если это из книги C, мне интересно, может ли быть более компактная реализация.
Rohit

30
@Rohit, для части полезного кода C он не может быть намного более компактным, чем это. Я полагаю, вы всегда можете удалить некоторые пробелы ...
Райан Калхун

8
почему здесь hashval = *s + 31 * hashval;именно 31 и больше ничего?
ア レ ッ ク ス

13
31 - простое число. Простые числа часто используются в хэш-функциях, чтобы уменьшить вероятность коллизий. Это как-то связано с целочисленной факторизацией (то есть вы не можете разложить на множители простое число).
jnovacho 04

2
@Overdrivr: в данном случае не требуется. hashtab имеет статическую продолжительность. Неинициализированные переменные со статической продолжительностью (то есть объявленные вне функций и объявленные с классом хранения static) гарантированно начинаются с нуля правильного типа (то есть: 0 или NULL или 0,0)
carveone

19

Самый быстрый способ - использовать уже существующую реализацию, например uthash .

И, если вы действительно хотите кодировать его самостоятельно, алгоритмы uthashможно изучить и использовать повторно. Он имеет лицензию BSD, поэтому, кроме требования об авторском праве, у вас есть довольно много ограничений в том, что вы можете с ним делать.


8

Для простоты реализации сложно превзойти наивный поиск по массиву. Если не считать проверки на ошибки, это полная реализация (непроверенная).

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
«Для простоты реализации»: Вы совершенно правы: это самый простой вариант. Кроме того, он реализует запрос OP «Я хочу, чтобы он мог хранить произвольное количество элементов» - ответ с наибольшим количеством голосов этого не делает (если вы не считаете, что выбор постоянной времени компиляции удовлетворяет «произвольному» ...)
davidbak 05

1
Это может быть допустимый подход в зависимости от варианта использования, но OP явно запросил словарь, а это определенно не словарь.
Дэн

3

Создайте простую хеш-функцию и несколько связанных списков структур, в зависимости от хеша, назначьте связанный список, в который нужно вставить значение. Используйте хэш для его получения.

Некоторое время назад я сделал простую реализацию:

...
#define K 16 // коэффициент сцепления

struct dict
{
    название символа; / * имя ключа * /
    int val; /* стоимость */
    struct dict * next; / * поле ссылки * /
};

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


void putval (char *, int);

void init_dict ()
{   
    инициализировано = 1;
    int i;  
    for (i = 0; iname = (char *) malloc (strlen (имя_ключа) +1);
    ptr-> val = sval;
    strcpy (ptr-> имя, имя_ключа);


    ptr-> next = (struct dict *) таблица [hsh];
    таблица [hsh] = ptr;

}


int getval (символ * имя_ключа)
{   
    int hsh = хэш (имя_ключа);   
    dict * ptr;
    для (ptr = table [hsh]; ptr! = (dict *) 0;
        ptr = (dict *) ptr-> следующий)
    если (strcmp (ptr-> имя, имя_ключа) == 0)
        вернуть ptr-> val;
    возврат -1;
}

1
Разве вы не упустили половину кода? где "hash ()" и "putval ()"?
swdev

3

GLib и гнулиб

Это ваш лучший выбор, если у вас нет более конкретных требований, поскольку они широко доступны, портативны и, вероятно, эффективны.

См. Также: Существуют ли библиотеки C с открытым исходным кодом с общими структурами данных?


2

вот быстрый способ, я использовал его, чтобы получить «матрицу» (sruct) из строки. вы можете иметь больший массив и также изменять его значения при запуске:

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

Я удивлен, что никто не упомянул набор библиотек hsearch / hcreate, который, хотя и недоступен в Windows, но обязателен POSIX и, следовательно, доступен в системах Linux / GNU.

Ссылка содержит простой и полный базовый пример, который очень хорошо объясняет ее использование.

У него даже есть потокобезопасный вариант, он прост в использовании и очень производительный.


2
Стоит отметить, что здесь люди говорят, что он непригоден для использования, хотя я сам не пробовал: stackoverflow.com/a/6118591/895245
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

1
Однако справедливо, но я пробовал версию hcreate_r (для нескольких хеш-таблиц) по крайней мере в одном приложении, которое работало достаточно долго, чтобы рассматривать ее в реальном мире. Согласились, что это расширение GNU, но то же самое и со многими другими библиотеками. Хотя я все равно буду утверждать, что вы все еще можете использовать его для одной большой пары ключевых значений, которая используется в каком-то реальном приложении
fkl

0

Хеш-таблица - это традиционная реализация простого «Словаря». Если вам не важны скорость или размер, просто погуглите . Есть много свободно доступных реализаций.

вот первый, который я увидел - на первый взгляд, мне кажется, что это нормально. (это довольно просто. Если вы действительно хотите, чтобы он содержал неограниченное количество данных, вам нужно добавить некоторую логику для «перераспределения» памяти таблицы по мере ее роста.)

удачи!


-1

Хеширование - это ключ. Я думаю, что для этого нужно использовать таблицу поиска и ключ хеширования. Вы можете найти множество функций хеширования в Интернете.


-2

Самый быстрый способ - использовать двоичное дерево. Его худший случай также только O (logn).


15
Это неверно. В худшем случае поиск двоичного дерева - O (n) (вырожденный случай из-за неправильного порядка вставки, в результате чего в основном получается список ссылок), когда оно не сбалансировано.
Рэнди Ховард
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.