Cara Cepat untuk Menerapkan Kamus di C

132

Salah satu hal yang saya rindukan saat menulis program di C adalah struktur data kamus. Apa cara paling nyaman untuk mengimplementasikannya di C? Saya tidak mencari kinerja, tetapi kemudahan mengkodekannya dari awal. Saya tidak ingin menjadi generik juga - sesuatu seperti string-> int akan dilakukan. Tapi saya ingin bisa menyimpan jumlah item yang sewenang-wenang.

Ini lebih dimaksudkan sebagai latihan. Saya tahu bahwa ada perpustakaan pihak ke-3 yang tersedia yang bisa digunakan. Tetapi pertimbangkan sejenak, bahwa mereka tidak ada. Dalam situasi seperti itu, apa cara tercepat Anda dapat mengimplementasikan kamus yang memenuhi persyaratan di atas.

Rohit
sumber
4
Jika Anda melewatkannya disediakan untuk Anda, lalu mengapa Anda ingin membuatnya dari awal, daripada menggunakan implementasi pihak ketiga?
Karl Knechtel
Ya, alternatif itu selalu ada. Saya mengajukan pertanyaan ini lebih sebagai latihan.
Rohit
10
Menulis hashtable di C adalah latihan yang menyenangkan - setiap programmer C yang serius harus melakukannya setidaknya sekali.
Lee
Saya menganggap kamus sebagai datatype daripada datastructure, karena bisa diterapkan banyak cara - daftar, hashtable, pohon, pohon self-balancing, dll. Apakah Anda meminta kamus, atau hashtable ?
Paul Hankin
1
Terkait: Bagaimana cara mewakili kamus seperti Python di C? [] ( Stackoverflow.com/questions/3269881/… )
Gaurang Tandon

Jawaban:

114

Bagian 6.6 dari Bahasa Pemrograman C menyajikan struktur data kamus sederhana (hashtabel). Saya tidak berpikir implementasi kamus yang berguna bisa menjadi lebih sederhana dari ini. Untuk kenyamanan Anda, saya mereproduksi kode di sini.

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

Perhatikan bahwa jika hash dari dua string bertabrakan, itu dapat menyebabkan O(n)waktu pencarian. Anda dapat mengurangi kemungkinan tabrakan dengan meningkatkan nilai HASHSIZE. Untuk diskusi lengkap tentang struktur data, silakan baca buku.

Vijay Mathew
sumber
1
Jika itu dari buku C, saya bertanya-tanya apakah bisa ada implementasi yang lebih kompak.
Rohit
30
@Rohit, untuk sepotong kode C yang bermanfaat, tidak ada yang lebih ringkas dari itu. Saya kira Anda selalu bisa menghapus beberapa spasi ...
Ryan Calhoun
7
mengapa di sini hashval = *s + 31 * hashval;tepatnya 31 dan bukan yang lain?
ア レ ッ ク ス
12
31 adalah prima. Primes sering digunakan dalam fungsi hash untuk mengurangi kemungkinan tabrakan. Ini ada hubungannya dengan faktorisasi bilangan bulat (yaitu Anda tidak dapat memperhitungkan faktor prima).
jnovacho
2
@ Overdrivr: Tidak perlu dalam hal ini. HashTab adalah durasi statis. Variabel tidak diinisialisasi dengan durasi statis (yaitu, yang dinyatakan di luar fungsi, dan yang dinyatakan dengan kelas penyimpanan statis), dijamin akan dimulai sebagai nol dari jenis yang tepat (yaitu: 0 atau NULL atau 0,0)
carveone
19

Cara tercepat adalah dengan menggunakan implementasi yang sudah ada, seperti uthash .

Dan, jika Anda benar - benar ingin membuat kode sendiri, algoritme dari uthashdapat diperiksa dan digunakan kembali. Ini dilisensikan BSD jadi, selain dari persyaratan untuk menyampaikan pemberitahuan hak cipta, Anda cukup tak terbatas dalam hal apa yang dapat Anda lakukan dengannya.

paxdiablo
sumber
8

Untuk kemudahan implementasi, sulit untuk mengalahkan pencarian secara naif melalui array. Selain dari beberapa pengecekan kesalahan, ini adalah implementasi lengkap (belum diuji).

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
sumber
2
"Untuk kemudahan implementasi": Anda memang benar: ini yang termudah. Plus itu mengimplementasikan permintaan OP "Saya ingin dapat menyimpan jumlah item yang sewenang-wenang" - jawaban dengan suara terbanyak tidak melakukan itu (kecuali jika Anda percaya bahwa memilih konstanta waktu kompilasi memenuhi "arbitrary" ...)
davidbak
1
Ini mungkin pendekatan yang valid tergantung pada kasus penggunaan, tetapi OP secara eksplisit meminta kamus, dan ini jelas bukan kamus.
Dan Bechard
3

Buat fungsi hash sederhana dan beberapa daftar struktur yang ditautkan, tergantung pada hash, tetapkan daftar tautan mana yang akan dimasukkan nilainya. Gunakan hash untuk mengambilnya juga.

Saya melakukan implementasi sederhana beberapa waktu lalu:

...
#define K 16 // koefisien rantai

dikte struct
{
    nama karakter; / * nama kunci * /
    int val; / * nilai * /
    struct dict * selanjutnya; / * bidang tautan * /
};

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


membatalkan putval (char *, int);

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


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

}


int getval (char * key_name)
{   
    int hsh = hash (key_name);   
    dict * ptr;
    untuk (ptr = table [hsh]; ptr! = (dict *) 0;
        ptr = (dict *) ptr-> selanjutnya)
    if (strcmp (ptr-> name, key_name) == 0)
        return ptr-> val;
    return -1;
}
abc def foo bar
sumber
1
Apakah Anda tidak kehilangan setengah kode? di mana "hash ()" dan "putval ()"?
swdev
3

GLib dan gnulib

Ini kemungkinan adalah taruhan terbaik Anda jika Anda tidak memiliki persyaratan yang lebih spesifik, karena tersedia secara luas, mudah dibawa-bawa dan kemungkinan efisien.

Lihat juga: Apakah ada pustaka sumber terbuka C dengan struktur data umum?

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

di sini adalah implementasi cepat, saya menggunakannya untuk mendapatkan 'Matrix' (sruct) dari sebuah string. Anda dapat memiliki array yang lebih besar dan mengubah nilainya saat dijalankan juga:

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
sumber
2

Saya terkejut tidak ada yang disebutkan hsearch / hcreate set perpustakaan yang walaupun tidak tersedia di windows, tetapi diamanatkan oleh POSIX, dan karena itu tersedia di sistem Linux / GNU.

Tautan ini memiliki contoh dasar yang sederhana dan lengkap yang menjelaskan penggunaannya dengan sangat baik.

Ia bahkan memiliki varian thread yang aman, mudah digunakan dan sangat performant.

fkl
sumber
2
Perlu dicatat bahwa orang-orang di sini mengatakan itu agak tidak dapat digunakan, walaupun saya belum mencobanya sendiri: stackoverflow.com/a/6118591/895245
Ciro Santilli 郝海东 冠状 病 六四 六四 事件 法轮功
1
Cukup adil, namun, saya telah mencoba versi hcreate_r (untuk beberapa tabel hash) di setidaknya satu aplikasi yang berjalan cukup lama untuk menganggapnya sebagai dunia nyata. Setuju bahwa ini merupakan ekstensi GNU tetapi kemudian demikian halnya dengan banyak lib lainnya juga. Meskipun saya masih berpendapat bahwa Anda mungkin masih dapat menggunakannya untuk satu pasangan nilai kunci besar yang dioperasikan di beberapa aplikasi dunia nyata
fkl
0

Hashtable adalah implementasi tradisional dari "Kamus" sederhana. Jika Anda tidak peduli dengan kecepatan atau ukuran, cukup google untuk itu . Ada banyak implementasi yang tersedia secara bebas.

inilah yang pertama saya lihat - sekilas, itu terlihat ok untuk saya. (Ini cukup mendasar. Jika Anda benar-benar ingin menyimpan data dalam jumlah yang tidak terbatas, maka Anda perlu menambahkan beberapa logika untuk "realokasi" memori tabel saat itu tumbuh.)

semoga berhasil!

Lee
sumber
-1

Hashing adalah kuncinya. Saya pikir menggunakan tabel pencarian dan kunci hashing untuk ini. Anda dapat menemukan banyak fungsi hashing online.

ashmish2
sumber
-1

Metode tercepat akan menggunakan pohon biner. Kasus terburuknya juga hanya O (logn).

cprogrammer
sumber
15
Ini salah. Pencarian kasus terburuk untuk pohon biner adalah O (n) (kasus degenerasi karena urutan penyisipan yang buruk, menghasilkan daftar tautan, pada dasarnya) ketika tidak seimbang.
Randy Howard