C에서 사전을 구현하는 빠른 방법


132

C로 프로그램을 작성하는 동안 내가 놓친 것 중 하나는 사전 데이터 구조입니다. C로 구현하는 가장 편리한 방법은 무엇입니까? 성능을 찾고 있지 않지만 처음부터 쉽게 코딩 할 수 있습니다. string-> int와 같은 일반적인 것이기를 원하지 않습니다. 그러나 임의의 수의 항목을 저장할 수 있기를 바랍니다.

이것은 운동으로 의도 된 것입니다. 사용할 수있는 타사 라이브러리가 있음을 알고 있습니다. 그러나 존재하지 않는 것을 잠시 생각해보십시오. 이러한 상황에서 위의 요구 사항을 충족하는 사전을 구현할 수있는 가장 빠른 방법은 무엇입니까?


4
제공하지 않은 경우 타사 구현을 사용하는 대신 왜 처음부터 작성하고 싶습니까?
Karl Knechtel

그렇습니다. 그 대안은 항상 존재합니다. 나는이 질문을 더 연습으로 제기했다.
Rohit

10
C로 해시 테이블을 작성하는 것은 재미있는 연습입니다. 모든 심각한 C 프로그래머는 적어도 한 번은 해봐야합니다.
Lee

사전은 데이터 구조가 아닌 데이터 유형이라고 생각합니다. 목록, 해시 테이블, 트리, 자체 균형 트리 등 여러 가지 방법으로 구현할 수 있기 때문입니다. 사전 또는 해시 테이블을 요청하십니까? ?
Paul Hankin

1
관련 : 파이썬과 같은 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;
}

두 문자열의 해시가 충돌하면 O(n)조회 시간이 발생할 수 있습니다 . 값을 증가시켜 충돌 가능성을 줄일 수 있습니다 HASHSIZE. 데이터 구조에 대한 자세한 내용은이 책을 참조하십시오.


1
C 서적에서 나온다면 더 컴팩트 한 구현이 가능한지 궁금합니다.
Rohit

30
@Rohit, 유용한 C 코드 조각은 그보다 훨씬 작지 않습니다. 난 당신이 항상 공백을 제거 할 수 있다고 생각합니다 ...
Ryan Calhoun

7
왜 여기에 hashval = *s + 31 * hashval;정확히 31이 있고 다른 것이 없습니까?
ア レ ッ ク ス

12
31은 소수입니다. 프라임은 종종 충돌 가능성을 줄이기 위해 해시 함수에 사용됩니다. 정수 인수 분해와 관련이 있습니다 (즉, 소수를 인수 분해 할 수 없음).
jnovacho

2
@Overdrivr :이 경우에는 필요하지 않습니다. hashtab은 정적 지속 시간입니다. 정적 지속 시간을 가진 초기화되지 않은 변수 (즉, 함수 외부에서 선언 된 변수 및 정적 저장소 클래스로 선언 된 변수)는 올바른 유형의 0 (예 : 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
{
    char * 이름; / * 키 이름 * /
    int val; / * 값 * /
    구조체 dict * next; / * 링크 필드 * /
};

typedef 구조체 dict dict;
dict * table [K];
int initialized = 0;


무효 퍼발 (char *, int);

무효 init_dict ()
{   
    초기화 = 1;
    int i;  
    for (i = 0; iname = (char *) malloc (strlen (key_name) +1);
    ptr-> val = 스발;
    strcpy (ptr-> 이름, key_name);


    ptr-> 다음 = (struct dict *) table [hsh];
    테이블 [hsh] = ptr;

}


int getval (char * key_name)
{   
    int hsh = 해시 (키 _ 이름);   
    dict * ptr;
    for (ptr = table [hsh]; ptr! = (dict *) 0;
        ptr = (dict *) ptr-> 다음)
    if (strcmp (ptr-> name, key_name) == 0)
        리턴 ptr-> val;
    리턴 -1;
}

1
코드 절반이 빠지지 않았습니까? "hash ()"와 "putval ()"은 어디에 있습니까?
swdev

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

윈도우에서는 사용할 수 없지만 POSIX에서 명령을 받아 Linux / GNU 시스템에서 사용할 수있는 hsearch / hcreate 라이브러리 세트에 대해 언급 한 사람이 아무도 없습니다.

링크에는 사용법을 잘 설명하는 간단하고 완전한 기본 예가 있습니다.

스레드 안전 변형이 있으며 사용하기 쉽고 성능이 뛰어납니다.


2
: 나 자신 그것을 시도하지 않은 있지만 여기 사람들은이 종류를 사용할 수 없게의라고 지적 가치 stackoverflow.com/a/6118591/895245
치로 틸리郝海东冠状病六四事件法轮功

1
그러나 충분히 공평하지만, 나는 적어도 하나의 앱에서 hcreate_r (여러 해시 테이블의 경우) 버전을 실제 세계로 간주하기에 충분한 시간 동안 실행했습니다. GNU 확장이라는 데 동의했지만 다른 많은 라이브러리에서도 마찬가지입니다. 난 여전히 당신은 여전히 하나의 큰 키 값 쌍은 몇 가지 실제 응용 프로그램에서 운영되고 그것을 사용할 수있을 것으로 주장하지만
FKL

0

해시 테이블은 간단한 "사전"의 전통적인 구현입니다. 속도 나 크기에 신경 쓰지 않는다면 google 만 찾으 십시오 . 자유롭게 사용할 수있는 구현이 많이 있습니다.

여기 제가 처음 본 것이 있습니다. 한 눈에보기에는 괜찮습니다. (매우 기본입니다. 데이터를 무제한으로 보유하려면 테이블 메모리가 증가함에 따라 "재 할당"하는 논리를 추가해야합니다.)

행운을 빕니다!


-1

해싱이 핵심입니다. 룩업 테이블과 해싱 키를 사용한다고 생각합니다. 온라인에서 많은 해싱 기능을 찾을 수 있습니다.


-1

가장 빠른 방법은 이진 트리를 사용하는 것입니다. 최악의 경우도 O (logn)입니다.


15
이것은 올바르지 않습니다. 이진 트리에 대한 최악의 경우 조회는 불균형 상태 일 때 O (n) (삽입 순서가 잘못되어 기본적으로 링크 목록이 생성되는 경우)입니다.
랜디 하워드
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.