Швидкий спосіб впровадження словника в C


132

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

Це задумано більше як вправа. Я знаю, що існують сторонні бібліотеки, якими можна користуватися. Але подумайте на хвилину, що їх не існує. У такій ситуації найшвидшим способом можна реалізувати словник, що відповідає вищевказаним вимогам.


4
Якщо ви сумуєте за тим, щоб це було передбачено для вас, то чому ви хочете зробити це з нуля, а не використовувати сторонню реалізацію?
Карл Кнечтел

Так, така альтернатива завжди існує. Я поставив це питання більше як вправу.
Рохіт

10
Написання хешшю на C - це захоплююча вправа - кожен серйозний програміст на C повинен робити це хоча б раз.
Лі

Я думаю, що словник є типом даних, а не структурою даних, оскільки він може бути реалізований безліччю способів - список, хештел, дерево, дерево, що самоврівноважує і т.д. ?
Пол Ханкін

1
Пов'язане: Як представити словник, схожий на Python в C? [] ( Stackoverflow.com/questions/3269881/… )
Gaurang Tandon

Відповіді:


114

Розділ 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
Якщо це з книги С, мені цікаво, чи може бути більш компактна реалізація.
Рохіт

30
@Rohit, за шматок корисного коду С, він не стає набагато компактнішим за це. Я припускаю, що ви завжди можете видалити пробіл ...
Райан Калхун

7
чому тут hashval = *s + 31 * hashval;рівно 31, а нічого іншого?
ア レ ッ ク ス

12
31 є простим. Прайми часто використовуються в хеш-функціях, щоб зменшити ймовірність зіткнень. Це має щось спільне з цілочисельною факторизацією (тобто ви не можете розраховувати просту).
jnovacho

2
@Overdrivr: Не потрібно в цьому випадку. хештаб має статичну тривалість. Неініціалізовані змінні зі статичною тривалістю (тобто ті, які оголошені поза функціями, і ті, які оголошені статичним класом зберігання), гарантовано починаються як нуль потрібного типу (тобто: 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
"Для зручності реалізації": Ви абсолютно праві: це найпростіше. Крім того, він реалізує запит ОП "Я хочу, щоб він міг зберігати довільну кількість предметів" - найвища відповідь, що проголосувала, цього не робить (якщо ви не вважаєте, що вибір константи часу компіляції задовольняє "довільну" ...)
davidbak

1
Це може бути правильним підходом, залежно від випадку використання, але ОП явно запитував словник, і це, безумовно, не словник.
Ден Бешард

3

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

Деякий час я зробив просту реалізацію:

...
#define K 16 // коефіцієнт ланцюга

структура дикту
{
    char * ім'я; / * ім'я ключа * /
    int val; / * значення * /
    структура диктату * далі; / * поле посилання * /
};

typedef struct dict dict;
dict * таблиця [K];
int ініціалізований = 0;


пустота путвал (char *, int);

недійсний init_dict ()
{   
    ініціалізований = 1;
    int i;  
    for (i = 0; iname = (char *) malloc (strlen (key_name) +1);
    ptr-> val = sval;
    strcpy (ptr-> ім'я, ім'я_кодрата);


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

}


int getval (char * key_name)
{   
    int hsh = хеш (ім'я_користувача);   
    dict * ptr;
    for (ptr = table [hsh]; ptr! = (dict *) 0;
        ptr = (dict *) ptr-> далі)
    if (strcmp (ptr-> ім'я, ім'я ключа) == 0)
        повернути ptr-> val;
    повернення -1;
}

1
Ви не пропустили половину коду? де "хеш ()" і "путвал ()"?
swdev

3

GLib і gnulib

Це ваші ймовірні найкращі ставки, якщо у вас немає більш конкретних вимог, оскільки вони широко доступні, портативні та, ймовірно, ефективні.

Дивіться також: Чи існують бібліотеки 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
Чіро Сантіллі郝海东冠状病六四事件法轮功

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

0

Хештел - це традиційна реалізація простого "Словника". Якщо вам не важлива швидкість чи розмір, просто перейдіть на Google . Існує багато вільно доступних реалізацій.

ось перше, що я побачив - на перший погляд, мені це здається нормальним. (це досить базово. Якщо ви дійсно хочете, щоб він міг зберігати необмежену кількість даних, вам потрібно буде додати трохи логіки, щоб "перерозподілити" пам'ять таблиці під час зростання.)

Щасти!


-1

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


-1

Найшвидшим методом буде використання двійкового дерева. Найгірший випадок - це лише O (вхід).


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