วิธีที่รวดเร็วในการติดตั้งพจนานุกรมในภาษา C


132

สิ่งหนึ่งที่ฉันพลาดขณะเขียนโปรแกรมใน C คือโครงสร้างข้อมูลพจนานุกรม วิธีใดที่สะดวกที่สุดในการใช้งานใน C? ฉันไม่ได้ต้องการประสิทธิภาพ แต่ง่ายต่อการเขียนโค้ดตั้งแต่เริ่มต้น ฉันไม่ต้องการให้มันเป็นแบบทั่วไปเช่นกัน - บางอย่างเช่น string-> int จะทำ แต่ฉันต้องการให้สามารถจัดเก็บรายการได้ตามจำนวนที่กำหนด

โดยมีวัตถุประสงค์เพื่อเป็นการออกกำลังกาย ฉันรู้ว่ามีไลบรารีของบุคคลที่สามที่สามารถใช้ได้ แต่พิจารณาสักครู่ว่าไม่มีอยู่จริง ในสถานการณ์เช่นนี้วิธีใดเป็นวิธีที่เร็วที่สุดที่คุณสามารถใช้พจนานุกรมที่ตรงตามข้อกำหนดข้างต้น


4
หากคุณพลาดที่จะจัดเตรียมไว้ให้คุณทำไมคุณถึงต้องการสร้างตั้งแต่เริ่มต้นแทนที่จะใช้การใช้งานของบุคคลที่สาม
Karl Knechtel

ใช่ทางเลือกนั้นมีอยู่เสมอ ฉันตั้งคำถามนี้เป็นแบบฝึกหัด
Rohit

10
การเขียนแฮชแท็กใน C เป็นการออกกำลังกายที่สนุก - โปรแกรมเมอร์ C ที่จริงจังทุกคนควรทำอย่างน้อยหนึ่งครั้ง
ลี

ฉันคิดว่าพจนานุกรมเป็นประเภทข้อมูลมากกว่าโครงสร้างข้อมูลเนื่องจากสามารถใช้งานได้หลายวิธีเช่นรายการแฮชแท็กต้นไม้ต้นไม้ที่ปรับสมดุลในตัวเอง ฯลฯ คุณกำลังขอพจนานุกรมหรือแฮชแท็ก ?
Paul Hankin

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
ถ้ามาจากหนังสือ C ฉันสงสัยว่าจะมีการใช้งานที่กะทัดรัดกว่านี้ได้หรือไม่
Rohit

30
@Rohit สำหรับโค้ด C ที่มีประโยชน์มันไม่ได้กระชับมากไปกว่านั้น ฉันคิดว่าคุณสามารถลบช่องว่างออกได้เสมอ ...
Ryan Calhoun

7
ทำไมที่นี่hashval = *s + 31 * hashval;ถึง 31 ตรงและไม่ใช่อย่างอื่น?
アレックス

12
31 เป็นนายก Primes มักใช้ในฟังก์ชันแฮชเพื่อลดความน่าจะเป็นของการชนกัน มันมีบางอย่างที่เกี่ยวข้องกับการแยกตัวประกอบจำนวนเต็ม (กล่าวคือคุณไม่สามารถแยกตัวประกอบไพรม์ได้)
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
"เพื่อความสะดวกในการนำไปใช้": คุณพูดถูก: นี่เป็นวิธีที่ง่ายที่สุด นอกจากนี้ยังดำเนินการตามคำขอของ OP "ฉันต้องการให้สามารถจัดเก็บรายการได้ตามจำนวนที่กำหนด" - คำตอบที่ได้รับการโหวตสูงสุดจะไม่ทำเช่นนั้น (เว้นแต่คุณจะเชื่อว่าการเลือกค่าคงที่ของเวลาในการรวบรวมเป็นไปตาม "โดยพลการ" ... )
davidbak

1
นี่อาจเป็นแนวทางที่ถูกต้องขึ้นอยู่กับกรณีการใช้งาน แต่ OP ขอพจนานุกรมอย่างชัดเจนและนี่ไม่ใช่พจนานุกรมอย่างแน่นอน
Dan Bechard

3

สร้างฟังก์ชันแฮชอย่างง่ายและรายการโครงสร้างที่เชื่อมโยงบางส่วนขึ้นอยู่กับแฮชกำหนดรายการที่เชื่อมโยงเพื่อแทรกค่า ใช้แฮชเพื่อดึงข้อมูลเช่นกัน

ฉันใช้งานง่ายๆในช่วงเวลาหนึ่ง:

...
#define K 16 // ค่าสัมประสิทธิ์โซ่

โครงสร้าง dict
{
    ถ่าน * ชื่อ; / * ชื่อคีย์ * /
    int วาล; / * ค่า * /
    Struct dict * ถัดไป; / * ช่องลิงค์ * /
};

typedef struct dict dict;
ตาราง dict * [K];
int เริ่มต้น = 0;


เป็นโมฆะ putval (ถ่าน *, int);

เป็นโมฆะ init_dict ()
{   
    เริ่มต้น = 1;
    int ฉัน;  
    สำหรับ (i = 0; iname = (char *) malloc (strlen (key_name) +1);
    ptr-> val = sval;
    strcpy (ptr-> ชื่อ, key_name);


    ptr-> next = (struct dict *) ตาราง [hsh];
    ตาราง [hsh] = ptr;

}


int getval (ถ่าน * key_name)
{   
    int hsh = แฮช (key_name);   
    dict * ptr;
    สำหรับ (ptr = table [hsh]; ptr! = (dict *) 0;
        ptr = (dict *) ptr-> ถัดไป)
    ถ้า (strcmp (ptr-> ชื่อ, key_name) == 0)
        ส่งคืน ptr-> val;
    กลับ -1;
}

1
คุณไม่มีรหัสครึ่งหนึ่งหรือไม่? "hash ()" และ "putval ()" อยู่ที่ไหน
swdev

3

GLib และ gnulib

นี่เป็นทางออกที่ดีที่สุดสำหรับคุณหากคุณไม่มีข้อกำหนดที่เฉพาะเจาะจงมากขึ้นเนื่องจากมีให้บริการอย่างกว้างขวางพกพาได้และมีแนวโน้มที่จะมีประสิทธิภาพ

ดูเพิ่มเติม: มีไลบรารี C โอเพ่นซอร์สที่มีโครงสร้างข้อมูลทั่วไปหรือไม่


2

นี่คือการนำไปใช้อย่างรวดเร็วฉันใช้มันเพื่อรับ 'Matrix' (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 แต่นั่นก็เป็นกรณีของ libs อื่น ๆ เช่นกัน แม้ว่าฉันจะยังคงเถียงว่าคุณอาจยังใช้งานได้กับคู่ค่าคีย์ขนาดใหญ่คู่หนึ่งที่ใช้งานในแอปในโลกแห่งความจริง
fkl

0

แฮชแท็กคือการนำ "พจนานุกรม" แบบธรรมดามาใช้ หากคุณไม่สนใจเกี่ยวกับความเร็วหรือขนาดเพียงgoogle สำหรับมัน มีการใช้งานฟรีมากมาย

นี่คืออันแรกที่ฉันเห็น - โดยรวมแล้วมันก็ดูโอเคสำหรับฉัน (มันค่อนข้างธรรมดาถ้าคุณต้องการให้มันเก็บข้อมูลได้ไม่ จำกัด จำนวนจริงๆคุณจะต้องเพิ่มตรรกะบางอย่างเพื่อ "จัดสรรใหม่" ให้กับหน่วยความจำตารางเมื่อมันเติบโตขึ้น)

โชคดี!


-1

การแฮชเป็นกุญแจสำคัญ ฉันคิดว่าใช้ตารางการค้นหาและแฮชคีย์สำหรับสิ่งนี้ คุณสามารถค้นหาฟังก์ชันการแฮชออนไลน์ได้มากมาย


-1

วิธีที่เร็วที่สุดคือการใช้ต้นไม้ไบนารี กรณีที่เลวร้ายที่สุดคือ O (logn) เท่านั้น


15
สิ่งนี้ไม่ถูกต้อง การค้นหากรณีที่เลวร้ายที่สุดสำหรับต้นไม้ไบนารีคือ O (n) (ตัวพิมพ์เล็กลงเนื่องจากลำดับการแทรกที่ไม่ถูกต้องส่งผลให้มีรายการลิงก์โดยทั่วไป) เมื่อมันไม่สมดุล
Randy Howard
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.