Cách nhanh chóng để thực hiện từ điển trong C


132

Một trong những điều tôi bỏ lỡ khi viết chương trình bằng C là cấu trúc dữ liệu từ điển. Cách thuận tiện nhất để thực hiện một trong C là gì? Tôi không tìm kiếm hiệu suất, nhưng dễ dàng mã hóa nó từ đầu. Tôi cũng không muốn nó chung chung - một cái gì đó như chuỗi-> int sẽ làm. Nhưng tôi muốn nó có thể lưu trữ một số lượng mục tùy ý.

Điều này được dự định nhiều hơn như là một bài tập. Tôi biết rằng có thư viện bên thứ 3 có sẵn mà ai đó có thể sử dụng. Nhưng hãy cân nhắc một chút, rằng chúng không tồn tại. Trong tình huống như vậy, cách nhanh nhất bạn có thể thực hiện một từ điển thỏa mãn các yêu cầu trên.


4
Nếu bạn bỏ lỡ việc cung cấp cho bạn, vậy tại sao bạn muốn làm lại từ đầu, thay vì sử dụng triển khai của bên thứ ba?
Karl Knechtel

Vâng, sự thay thế đó luôn tồn tại. Tôi đặt ra câu hỏi này nhiều hơn như một bài tập.
Rohit

10
Viết một hashtable bằng C là một bài tập thú vị - mỗi lập trình viên C nghiêm túc nên làm điều đó ít nhất một lần.
Lee

Tôi nghĩ rằng một từ điển là một kiểu dữ liệu chứ không phải là một cơ sở hạ tầng, vì nó có thể được thực hiện theo nhiều cách - một danh sách, một hashtable, một cây, một cây tự cân bằng, v.v. Bạn đang yêu cầu một từ điển, hoặc một hashtable ?
Paul Hankin

1
Liên quan: Làm thế nào để đại diện cho một Python giống như từ điển trong C [] (? Stackoverflow.com/questions/3269881/... )
Gaurang Tandon

Câu trả lời:


114

Phần 6.6 của Ngôn ngữ lập trình C trình bày cấu trúc dữ liệu từ điển (hashtable) đơn giản. Tôi không nghĩ rằng việc thực hiện từ điển hữu ích có thể đơn giản hơn việc này. Để thuận tiện cho bạn, tôi sao chép mã ở đây.

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

Lưu ý rằng nếu băm của hai chuỗi va chạm, nó có thể dẫn đến O(n)thời gian tra cứu. Bạn có thể giảm khả năng va chạm bằng cách tăng giá trị của HASHSIZE. Để thảo luận đầy đủ về cấu trúc dữ liệu, vui lòng tham khảo sách.


1
Nếu đó là từ cuốn sách C, tôi tự hỏi liệu có thể có một triển khai nhỏ gọn hơn không.
Rohit

30
@Rohit, đối với một đoạn mã C hữu ích, nó không gọn hơn nhiều. Tôi cho rằng bạn luôn có thể xóa một số khoảng trắng ...
Ryan Calhoun

7
Tại sao ở đây hashval = *s + 31 * hashval;chính xác là 31 mà không phải bất cứ điều gì khác?
ア レ ッ

12
31 là số nguyên tố. Các số nguyên tố thường được sử dụng trong các hàm băm để giảm xác suất va chạm. Nó có một cái gì đó để làm với yếu tố số nguyên (nghĩa là bạn không thể tính một số nguyên tố).
jnovacho

2
@Overdrivr: Không cần thiết trong trường hợp này. hashtab có thời lượng tĩnh. Các biến chưa được khởi tạo với thời lượng tĩnh (nghĩa là các biến được khai báo bên ngoài hàm và các biến được khai báo với lớp lưu trữ tĩnh), được đảm bảo bắt đầu bằng 0 của loại đúng (ví dụ: 0 hoặc NULL hoặc 0,0)
carveone

19

Cách nhanh nhất sẽ là sử dụng một triển khai đã có sẵn, như uthash .

Và, nếu bạn thực sự muốn tự viết mã, các thuật toán từ uthashcó thể được kiểm tra và sử dụng lại. Nó được cấp phép BSD, ngoài yêu cầu truyền đạt thông báo bản quyền, bạn hoàn toàn không giới hạn trong những gì bạn có thể làm với nó.


8

Để dễ thực hiện, thật khó để đánh bại việc tìm kiếm một cách ngây thơ qua một mảng. Ngoài một số kiểm tra lỗi, đây là một thực hiện đầy đủ (chưa được kiểm tra).

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
"Để dễ thực hiện": Bạn hoàn toàn đúng: đây là cách dễ nhất. Thêm vào đó, nó thực hiện yêu cầu của OP "Tôi thực sự muốn nó có thể lưu trữ một số lượng vật phẩm tùy ý" - câu trả lời được bình chọn cao nhất không làm được điều đó (trừ khi bạn tin rằng việc chọn hằng số thời gian biên dịch thỏa mãn "tùy ý" ...)
davidbak

1
Đây có thể là một cách tiếp cận hợp lệ tùy thuộc vào trường hợp sử dụng, nhưng OP rõ ràng đã yêu cầu một từ điển và đây chắc chắn không phải là một từ điển.
Dan Bechard

3

Tạo một hàm băm đơn giản và một số danh sách cấu trúc được liên kết, tùy thuộc vào hàm băm, gán danh sách được liên kết để chèn giá trị vào. Sử dụng hàm băm để lấy nó là tốt.

Tôi đã thực hiện một cách đơn giản một thời gian trước:

...
#define K 16 // hệ số chuỗi

cấu trúc chính tả
{
    tên nhân vật; / * tên của khóa * /
    int val; /* giá trị */
    cấu trúc dict * tiếp theo; / * trường liên kết * /
};

typedef struct dict dict;
dict * bảng [K];
int khởi tạo = 0;


void putval (char *, int);

void init_dict ()
{   
    khởi tạo = 1;
    int i;  
    for (i = 0; iname = (char *) malloc (strlen (key_name) +1);
    ptr-> val = sval;
    strcpy (ptr-> tên, key_name);


    ptr-> next = (struct dict *) bảng [hsh];
    bảng [hsh] = ptr;

}


int getval (char * key_name)
{   
    int hsh = hash (key_name);   
    dict * ptr;
    for (ptr = bảng [hsh]; ptr! = (dict *) 0;
        ptr = (dict *) ptr-> tiếp theo)
    if (strcmp (ptr-> name, key_name) == 0)
        trả lại ptr-> val;
    trả về -1;
}

1
Bạn không thiếu một nửa mã? "hash ()" và "putval ()" ở đâu?
swdev

3

GLib và gnulib

Đây là những cược tốt nhất của bạn nếu bạn không có yêu cầu cụ thể hơn, vì chúng có sẵn rộng rãi, di động và có khả năng hiệu quả.

Xem thêm: Có thư viện C nguồn mở nào có cấu trúc dữ liệu chung không?


2

Đây là một triển khai nhanh, tôi đã sử dụng nó để lấy 'Ma trận' (cấu trúc) từ một chuỗi. bạn có thể có một mảng lớn hơn và thay đổi giá trị của nó khi chạy:

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

Tôi ngạc nhiên không ai đề cập đến bộ thư viện hsearch / hcreate , mặc dù không có sẵn trên windows, nhưng được ủy quyền bởi POSIX, và do đó có sẵn trong các hệ thống Linux / GNU.

Liên kết có một ví dụ cơ bản đơn giản và đầy đủ giải thích rất rõ cách sử dụng của nó.

Nó thậm chí có biến thể an toàn chủ đề, dễ sử dụng và rất hiệu quả.


2
Đáng chú ý là mọi người ở đây nói rằng nó không thể sử dụng được, mặc dù tôi đã không tự mình thử: stackoverflow.com/a/6118591/895245
Ciro Santilli 冠状 病 六四 事件 法轮功

1
Tuy nhiên, đủ công bằng, tôi đã thử phiên bản hcreate_r (cho nhiều bảng băm) trong ít nhất một ứng dụng chạy trong một thời gian đủ dài để coi đó là thế giới thực. Đồng ý rằng đó là một phần mở rộng GNU nhưng sau đó cũng là trường hợp của nhiều lib khác. Mặc dù tôi vẫn sẽ tranh luận rằng bạn vẫn có thể sử dụng nó cho một cặp giá trị khóa lớn đang được vận hành trong một số ứng dụng trong thế giới thực
fkl

0

Một hashtable là cách thực hiện truyền thống của một "Từ điển" đơn giản. Nếu bạn không quan tâm đến tốc độ hoặc kích thước, chỉ cần google cho nó . Có rất nhiều triển khai miễn phí có sẵn.

Đây là cái đầu tiên tôi nhìn thấy - trong nháy mắt, nó có vẻ ổn với tôi. (nó khá cơ bản. Nếu bạn thực sự muốn nó chứa một lượng dữ liệu không giới hạn, thì bạn sẽ cần thêm một số logic để "phân bổ lại" bộ nhớ bảng khi nó phát triển.)

chúc may mắn!


-1

Băm là chìa khóa. Tôi nghĩ sử dụng bảng tra cứu và phím băm cho việc này. Bạn có thể tìm thấy nhiều chức năng băm trực tuyến.


-1

Phương pháp nhanh nhất sẽ là sử dụng cây nhị phân. Trường hợp xấu nhất của nó cũng chỉ là O (logn).


15
Điều này là không đúng. Trường hợp xấu nhất tìm kiếm cây nhị phân là O (n) (trường hợp suy biến do thứ tự chèn xấu, dẫn đến danh sách liên kết, về cơ bản) khi nó không cân bằng.
Randy Howard
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.