C Sözlüğü Uygulamasının Hızlı Yolu


132

C'de program yazarken özlediğim şeylerden biri sözlük veri yapısıdır. Birini C'de uygulamanın en uygun yolu nedir? Performans arıyorum değil, sıfırdan kodlama kolaylığı. Ben de genel olmasını istemiyorum - string-> int gibi bir şey yapacak. Ancak, rastgele sayıda öğeyi depolayabilmesini istiyorum.

Bu daha çok bir egzersiz olarak düşünülmüştür. Kullanılabilecek 3. taraf kitaplıkları olduğunu biliyorum. Ama bir an için var olmadıklarını düşünün. Böyle bir durumda, yukarıdaki gereksinimleri karşılayan bir sözlüğü uygulamanın en hızlı yolu budur.


4
Sizin için sağlanmasını özlediyseniz, üçüncü taraf bir uygulama kullanmak yerine neden sıfırdan yapmak istiyorsunuz?
Karl Knechtel

Evet, bu alternatif her zaman vardır. Bu soruyu daha çok bir egzersiz olarak sordum.
Rohit

10
C'de bir hashtable yazmak eğlenceli bir alıştırmadır - her ciddi C programcısı bunu en az bir kez yapmalıdır.
Lee

Bir sözlüğün veri yapısı olmaktan ziyade bir veri türü olduğunu düşünüyorum, çünkü birçok şekilde uygulanabilir - bir liste, bir hashtable, bir ağaç, kendi kendini dengeleyen bir ağaç vb. Bir sözlük veya bir hashtable mı istiyorsunuz? ?
Paul Hankin

1
İlgili: Python benzeri bir sözlük C
dilinde

Yanıtlar:


114

C Programlama Dili Bölüm 6.6, basit bir sözlük (hashtable) veri yapısı sunar. Yararlı bir sözlük uygulamasının bundan daha basit olabileceğini düşünmüyorum. Size kolaylık sağlamak için kodu burada yeniden oluşturuyorum.

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

İki dizenin karmaları çakışırsa, bir O(n)arama süresine yol açabileceğini unutmayın . Değerini artırarak çarpışma olasılığını azaltabilirsiniz HASHSIZE. Veri yapısının eksiksiz bir tartışması için lütfen kitaba bakın.


1
C kitabındansa, daha kompakt bir uygulama olup olmayacağını merak ediyorum.
Rohit

30
@Rohit, kullanışlı bir C kodu için bundan çok daha kompakt olamaz. Sanırım her zaman biraz boşluk kaldırabilirsin ...
Ryan Calhoun

7
neden burası hashval = *s + 31 * hashval;tam olarak 31 ve başka bir şey değil?
ア レ ッ ク ス

12
31 asaldır. Asal sayılar genellikle hash fonksiyonlarında çarpışma olasılığını azaltmak için kullanılır. Bunun tamsayı çarpanlara ayırmayla ilgisi vardır (yani bir asal çarpanlara ayıramazsınız).
jnovacho

2
@Overdrivr: Bu durumda gerekli değil. hashtab statik sürelidir. Statik süreye sahip başlatılmamış değişkenlerin (yani, işlevlerin dışında bildirilenler ve statik depolama sınıfı ile bildirilenler), doğru türün sıfır olarak başlaması garanti edilir (yani: 0 veya NULL veya 0.0)
carveone

19

En hızlı yol, uthash gibi halihazırda var olan bir uygulamayı kullanmaktır .

Ve gerçekten kendiniz kodlamak istiyorsanız, algoritmalar uthashincelenebilir ve yeniden kullanılabilir. BSD lisanslıdır, bu nedenle telif hakkı bildirimini iletme gerekliliği dışında, onunla yapabilecekleriniz konusunda oldukça sınırsızsınız.


8

Uygulama kolaylığı için, bir dizi içinde safça arama yapmak zor. Bazı hata kontrollerinin yanı sıra, bu tam bir uygulamadır (test edilmemiştir).

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
"Uygulama kolaylığı için": Kesinlikle haklısınız: bu en kolayı. Ayrıca, OP'nin "Rasgele sayıda öğeyi saklayabilmesini istiyorum" talebini uygular - en yüksek oylanan cevap bunu yapmaz ( derleme süresi sabiti seçmenin "keyfi" yi tatmin ettiğine inanmıyorsanız ...)
davidbak

1
Bu, kullanım durumuna bağlı olarak geçerli bir yaklaşım olabilir, ancak OP açıkça bir sözlük talep etti ve bu kesinlikle bir sözlük değil.
Dan Bechard

3

Basit bir hash fonksiyonu ve hash'e bağlı olarak bazı bağlantılı yapı listeleri oluşturun, değerin ekleneceği bağlı listeyi atayın. Karmayı almak için de kullanın.

Bir süre önce basit bir uygulama yaptım:

...
#define K 16 // zincirleme katsayısı

yapı diktesi
{
    karakter ismi; / * anahtarın adı * /
    int val; / * değer * /
    struct dict * sonraki; / * bağlantı alanı * /
};

typedef struct diktesi;
dikte * tablo [K];
int başlatılmış = 0;


void putval (char *, int);

void init_dict ()
{   
    başlatılmış = 1;
    int i;  
    for (i = 0; iname = (char *) malloc (strlen (key_name) +1);
    ptr-> val = sval;
    strcpy (ptr-> ad, anahtar_adı);


    ptr-> sonraki = (struct dict *) tablo [hsh];
    tablo [hsh] = ptr;

}


int getval (karakter * anahtar_adı)
{   
    int hsh = hash (anahtar_adı);   
    dikte * ptr;
    için (ptr = tablo [hsh]; ptr! = (dict *) 0;
        ptr = (dict *) ptr-> sonraki)
    eğer (strcmp (ptr-> ad, anahtar_adı) == 0)
        dönüş ptr-> val;
    dönüş -1;
}

1
Kodun yarısını kaçırmadın mı? "hash ()" ve "putval ()" nerede?
swdev

3

GLib ve gnulib

Yaygın olarak mevcut, taşınabilir ve muhtemelen verimli olduklarından, daha spesifik gereksinimleriniz yoksa bunlar muhtemelen en iyi bahislerinizdir.

Ayrıca bkz: Ortak veri yapılarına sahip herhangi bir açık kaynak C kitaplığı var mı?


2

işte hızlı bir uygulama, onu bir dizeden bir 'Matrix' (sruct) elde etmek için kullandım. daha büyük bir diziye sahip olabilir ve çalışma sırasında değerlerini de değiştirebilirsiniz:

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

Hiç kimsenin hsearch / hcreate kitaplık setinden bahsetmediğine şaşırdım , ancak bunlar pencerelerde bulunmaz, ancak POSIX tarafından yetkilendirilir ve bu nedenle Linux / GNU sistemlerinde mevcuttur.

Bağlantı, kullanımını çok iyi açıklayan basit ve eksiksiz bir temel örneğe sahiptir.

Hatta iş parçacığı için güvenli bir çeşidi vardır, kullanımı kolaydır ve çok performanslıdır.


2
Kendim denememiş olsam da, buradaki insanların biraz kullanışsız olduğunu söylediklerini belirtmek gerekir: stackoverflow.com/a/6118591/895245
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

1
Yeterince adil, ancak, hcreate_r (birden çok karma tablo için) sürümünü, gerçek dünya olarak kabul etmek için oldukça uzun bir süre çalışan en az bir uygulamada denedim. Bunun bir GNU uzantısı olduğu konusunda hemfikiriz, ancak diğer birçok kitap için de durum böyledir. Yine de bazı gerçek dünya uygulamalarında çalıştırılan bir büyük anahtar değer çifti için hala kullanabileceğinizi iddia ediyor
olsam da

0

Bir hashtable, basit bir "Sözlük" ün geleneksel uygulamasıdır. Hızı veya boyutu önemsemiyorsanız, bunun için google . Ücretsiz olarak kullanılabilen birçok uygulama vardır.

işte ilk gördüğüm şey - bir bakışta bana iyi görünüyor. (Oldukça basit. Eğer gerçekten sınırsız miktarda veri tutmasını istiyorsanız, o zaman tablo belleğini büyüdükçe "yeniden tahsis etmek" için biraz mantık eklemeniz gerekir.)

iyi şanslar!


-1

Hashing anahtardır. Bunun için arama tablosu ve hashing anahtarı kullanmayı düşünüyorum. Birçok hashing işlevini çevrimiçi olarak bulabilirsiniz.


-1

En hızlı yöntem ikili ağaç kullanmaktır. En kötü durumu da sadece O (logn).


15
Bu yanlış. Bir ikili ağaç için en kötü durum araması, dengesiz olduğunda O (n) 'dir (kötü ekleme sırasından kaynaklanan dejenere durum, temelde bir bağlantı listesi ile sonuçlanır).
Randy Howard
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.