Cで辞書を実装する簡単な方法


132

Cでプログラムを書いているときに私が見落としているものの1つは、辞書データ構造です。Cで実装する最も便利な方法は何ですか?私はパフォーマンスを求めていませんが、ゼロからコーディングするのは簡単です。汎用的なものにもしたくありません-string-> intのようなもので十分です。ただし、任意の数のアイテムを格納できるようにしたいのですが。

これは演習として意図されています。使用できるサードパーティのライブラリがあることは知っています。しかし、少しの間、それらが存在しないことを考慮してください。このような状況で、上記の要件を満たす辞書を実装する最も速い方法は何ですか。


4
提供されていない場合、サードパーティの実装を使用するのではなく、なぜ最初から作成するのですか?
Karl Knechtel、2010

はい、その代替手段は常に存在します。私はこの質問を演習として提起しました。
Rohit

10
Cでハッシュテーブルを書くのは楽しい練習です-真面目なCプログラマは誰でも少なくとも1回はそれを行うべきです。
Lee

辞書は、データ構造ではなくデータ型であると思います。リスト、ハッシュテーブル、ツリー、自己バランスツリーなど、さまざまな方法で実装できます。辞書を求めていますか、それともハッシュテーブルですか。 ?
ポールハンキン、2013

1
関連:PythonのようなC [](中辞典表現する方法は?stackoverflow.com/questions/3269881/...
Gaurang Tandon

回答:


114

Cプログラミング言語のセクション6.6 は、単純な辞書(ハッシュテーブル)データ構造を示しています。便利な辞書の実装がこれより簡単になるとは思いません。参考までに、ここにコードを複製します。

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

2つの文字列のハッシュが衝突すると、O(n)ルックアップ時間が発生する可能性があることに注意してください。の値を増やすことで、衝突の可能性を減らすことができますHASHSIZE。データ構造の詳細については、本を参照してください。


1
Cの本からだとすれば、もっとコンパクトな実装ができるかどうか疑問に思います。
Rohit

30
@Rohit、便利なCコードの一部としては、それよりもはるかにコンパクトにはなりません。空白をいつでも削除できると思います...
Ryan Calhoun

7
なぜここにhashval = *s + 31 * hashval;ちょうど31があり、他に何がないのですか?
アレックス

12
31は素数です。プライムは、衝突の可能性を減らすためにハッシュ関数でよく使用されます。整数分解と関係があります(つまり、素数を分解できません)。
jnovacho 2014年

2
@Overdrivr:このインスタンスでは必要ありません。hashtabは静的な期間です。初期化されていない静的期間(ある、関数の外部宣言されるもの、及びストレージクラス静的と宣言されたもの)を持つ変数、右のタイプ(例: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 2015年

1
これはユースケースによっては有効なアプローチとなる場合がありますが、OPは明示的にディクショナリを要求しましたが、これは明らかにディクショナリではありません。
Dan Bechard

3

ハッシュに応じて、単純なハッシュ関数といくつかの構造のリンクリストを作成し、値を挿入するリンクリストを割り当てます。それを取得するためにもハッシュを使用します。

私はしばらく前に簡単な実装をしました:

...
#define K 16 //連鎖係数

struct dict
{
    char * name; / *キーの名前* /
    int val; / *値* /
    struct dict * next; / *リンクフィールド* /
};

typedef struct dict dict;
dict * table [K];
初期化されたint = 0;


void putval(char *、int);

void init_dict()
{   
    初期化済み= 1;
    int i;  
    for(i = 0; iname =(char *)malloc(strlen(key_name)+1);
    ptr-> val = sval;
    strcpy(ptr-> name、key_name);


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

}


int getval(char * key_name)
{   
    int hsh = hash(key_name);   
    dict * ptr;
    for(ptr = table [hsh]; ptr!=(dict *)0;
        ptr =(dict *)ptr-> next)
    if(strcmp(ptr-> name、key_name)== 0)
        ptr-> valを返します。
    -1を返します。
}

1
コードの半分が欠けていませんか?「hash()」と「putval()」はどこにありますか?
swdev 2015

3

GLibとgnulib

これらは広く利用可能で、移植性があり、効率的である可能性が高いため、特定の要件がない場合に最善の策です。

参照:共通のデータ構造を持つオープンソースのCライブラリはありますか?


2

ここに簡単な実装があります。文字列から「マトリックス」(構造)を取得するために使用しました。より大きな配列を作成し、実行時にその値を変更することもできます。

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

Windowsでは使用できませんが、POSIXによって義務付けられているため、Linux / GNUシステムで使用できるライブラリのhsearch / hcreateセットについて誰も言及していません。

リンクには、その使用法を非常によく説明するシンプルで完全な基本例があります。

スレッドセーフなバリアントもあり、使いやすく、非常に高性能です。


2
:私は自分自身にそれを試していないが、ここの人々は、それは一種使用できないのであると言うことは注目に値するstackoverflow.com/a/6118591/895245
チロSantilli郝海东冠状病六四事件法轮功

1
しかし、十分に公平ですが、現実世界と考えるのに十分な時間をかけて実行された少なくとも1つのアプリで、hcreate_r(複数のハッシュテーブル用)バージョンを試しました。これはGNU拡張であることに同意しましたが、それは他の多くのライブラリにも当てはまります。私はまだあなたがまだいくつかの現実世界のアプリで動作して一つの大きなキーと値のペアのためにそれを使用することができるかもしれないことを主張するだろうけど
FKL

0

ハッシュテーブルは、単純な「辞書」の従来の実装です。速度やサイズを気にしない場合は、Googleで検索してください。自由に利用できる実装はたくさんあります。

これが私が最初に見たものです -一見、それは私には大丈夫に見えます。(これはかなり基本的なことです。無制限の量のデータを保持したい場合は、テーブルメモリが大きくなるにつれて、テーブルメモリを「再割り当て」するためのロジックを追加する必要があります。)

幸運を!


-1

ハッシュが鍵です。これにはルックアップテーブルとハッシュキーを使用すると思います。あなたは多くのハッシュ関数をオンラインで見つけることができます。


-1

最も速い方法は、バイナリツリーを使用することです。最悪のケースもO(logn)のみです。


15
これは誤りです。バイナリツリーの最悪の場合のルックアップは、バランスが悪い場合、O(n)(挿入順序が悪いため縮退したケースであり、基本的にリンクリストが生成されます)です。
ランディハワード
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.