【问题标题】:In C, How can I use an array of strings as a lookup table?在 C 中,如何使用字符串数组作为查找表?
【发布时间】:2018-08-19 07:11:58
【问题描述】:

我太难了。我正在学习C 并且有这个问题:

如何使用字符串数组作为查找表?

我有一个“键”列表:

"A", "A#", "B", "Bb", "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#"

每个都将引用一个特定的 int '值'(不是唯一的)。 例如"A" -> 9"A#" -> 10"Bb" -> 10

我找到了一个答案 (store known key/value pairs in c),当它说“我会...建议仅使用字符串数组作为查找表”时,我认为它为我指明了正确的方向

但我不知道如何将字符串数组实际实现为查找表?

【问题讨论】:

  • 正确看待它。完整的解决方案将是哈希表。为什么?您有一个散列操作,它为您的每个键计算一个“键”,然后存储这些值,以便您的查找只是一个 table[hashed key]。与每次您想要执行查找时所有元素的潜在strcmp 相比,这非常有效——但正确编码也更加复杂。对于“学习”,一个简单的指向字符串的指针数组和一个关联的值数组就可以了。 (这里使用 2-char 键,您实际上可以比较 key[0][0]key[0][1]
  • @DavidC.Rankin 你是对的。哈希方法简单快速。我有一个简单的哈希运算来计算hashed key = index,然后查找只是一个value[hashed key]。对于 OP,我为他的密钥域定制并简化了算法。

标签: c hashtable


【解决方案1】:

由于您打算将字符串用作具有整数值的键,因此最好使用struct 来包含这样的一对。然后建立他们的表。最后,由于您一直小心地保持键的排序,您可以使用 C 库函数 bsearch 进行查找:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct pair {
  char *key;
  int value;
} PAIR;

// Key strings must be in strcmp() sorted order!
PAIR table[] = {
  {"A",  9}, {"A#", 10}, {"B",  11}, {"Bb", 10}, {"C", 11}, {"C#", 12}, {"D", 13},
  {"D#", 14}, {"E", 15}, {"F", 16}, {"F#", 17}, {"G", 18}, {"G#", 19},
};

static int compare_keys(const void *va, const void *vb) {
  const PAIR *a = va, *b = vb;
  return strcmp(a->key, b->key);
}

int get_value(char *key) {
  PAIR key_pair[1] = {{key}};
  PAIR *pair = bsearch(key_pair, table,
      sizeof table / sizeof table[0], sizeof table[0], compare_keys);
  return pair ? pair->value : -1;
}

int main(void) {
  // Partial test: verify we can look up all the valid keys.
  for (int i = 0; i < sizeof table / sizeof table[0]; ++i) {
    int value = get_value(table[i].key);
    printf("%s -> %d\n", table[i].key, value);
  }
  return 0;
}

【讨论】:

  • 不错的解决方案!简短而通用。
  • 非常感谢您的回复,非常感谢您的帮助
【解决方案2】:

我正在学习 C [...] 如何实际实现一个字符串数组作为查找表?

提出了两种解决方案。

第一个解决方案是满足 OP 的标准(对于 C 初学者来说很简单,并且已经索引了 lookup table)。 解决方案来自对领域的分析:密钥的性质。 对str_key表中的key进行合理排列后,导出lookup table对应索引的过程就非常简单了。 不需要密集的计算。没有使用strcmp或其他搜索功能。

我真的相信这是解决所提出问题的最短、最简单和最快的解决方案。

#include <stdio.h> 
// int index   =  { 0,  1,  2,  3,  4,  5,  6,   7,  8,   9,   10,  11,  12, 13,  14  };
int value[]    =  { 9, 10, 11, 13, 17, 19,  7,  10, 10,  12,   15,  -1,  20,  8,   0  }; 

char *str_key[] = {"A","B","C","D","E","F","G","A#","Bb","C#","D#","Eb","F#","G#",NULL};

int get_value(char *key, int v[])      // magic happens here!
{
    // From string key we build corresponding index to the `value` array
                                      // Note: index for key "A" == 0  
    int index = (int) (key[0] - 'A'); // One letter key will have index from 0 to 6

    if( key[1] !=0 )                  // two letter key have his index from 7 to 13   
    {
        index = index + 7;            // index for "A#" == 7
    }

    if( (index < 0) || (index > 13) ) // protection from bad keys
        return -1;  

    return v[index];                  // return the value
}

int main(void)
{
    for (int i = 0; str_key[i] != NULL; i++) {

        int v = get_value( str_key[i], value);

        printf("%2s -> %2d\n", str_key[i], v);
    }
    return 0;
}

测试:

 A ->  9                                                                                                                                        
 B -> 10                                                                                                                                        
 C -> 11                                                                                                                                        
 D -> 13                                                                                                                                        
 E -> 17                                                                                                                                        
 F -> 19                                                                                                                                        
 G ->  7                                                                                                                                        
A# -> 10                                                                                                                                        
Bb -> 10                                                                                                                                        
C# -> 12                                                                                                                                        
D# -> 15                                                                                                                                        
Eb -> -1                                                                                                                                        
F# -> 20                                                                                                                                        
G# ->  8 

第二个简单的解决方案:

第二种方案不使用查找表,但也易于实现、灵活且易于理解。

这是基于事实

  • 键集很小

  • 键的最大长度为2 个字符。

解决方案非常快。它避免了strcmp 或任何其他搜索功能。特定的键是以非常简单的方式从字符串构建的。

解决方案基于多字符常量

来自Wikipedia

多字符常量(例如'xy')是有效的,虽然很少 used — 他们让一个整数存储多个字符(例如 4 ASCII 字符可以容纳 32 位整数,8 位 64 位整数)。

6.4.4.4 Character constants: 整数字符常量是one or more multibyte characters enclosed in single-quotes 的序列,如'x'

gcc-Wall 下,代码应该在没有警告的情况下编译,在-pedantic 下会出现“multi-character character constant” 警告。 您可以使用-Wno-multichar 禁用警告。

#include <stdio.h>

char *str_key[] = {"A","B","C","D","E","F","G","A#","Bb","C#","D#","Eb","F#","G#",NULL};

int build_index (char *key)
{
 // The magic happens here:
 // We take the string eg. "A#" 
 // and we construct corresponding multi-character constant 'A#'
 //                 A                  # 
    int index;

    if( key[1] == 0 )
        index = (int) key[0]; 
    else
        index = (int) ( (key[0] << 8) | key[1] );

    return index;   
}

int get_value (int key)
{
  switch (key)
    {
    case 'A':   return 9;
    case 'A#':  return 10;
    case 'B':   return 10;
    case 'Bb':  return 10;
    case 'C':   return 11;
    case 'C#':  return 12;
    case 'D':   return 13;
    case 'D#':  return 15;
    case 'E':   return 17;        
    case 'F':   return 19;
    case 'F#':  return 20;
    case 'G':   return 7;          
    case 'G#':  return 8;      
    deafult:
      break;
    }
  return -1;
}

int main (void)
{
    for (int i = 0; str_key[i] != NULL; i++) {

        int v = get_value( build_index( str_key[i] ) );

        printf("%2s -> %2d\n", str_key[i], v);
    }
    return 0;
}

测试:

 A ->  9                                                                                                              
 B -> 10                                                                                                              
 C -> 11                                                                                                              
 D -> 13                                                                                                              
 E -> 17                                                                                                              
 F -> 19                                                                                                              
 G ->  7                                                                                                              
A# -> 10                                                                                                              
Bb -> 10                                                                                                              
C# -> 12                                                                                                              
D# -> 15                                                                                                              
Eb -> -1                                                                                          
F# -> 20                                                                                
G# ->  8                                                                    

查看解决方案,如果您还有其他问题,请告诉我。谢谢!

【讨论】:

  • 这太棒了!非常感谢
【解决方案3】:

你可以在这个练习中学到很多东西。缺少哈希表的最佳解决方案是使用stuct 来形成对,正如 Gene 在他的回答中所显示的那样。任何时候你需要协调不同类型的不相关值,你应该考虑struct

但是,根据您的问题,尚不清楚您是否可以使用struct 来解决您的问题。如果不是,那么将不同类型的值关联起来的一种开始方式是简单地使用两个数组,其中键/值关联由 数组索引 提供。

正如我在对您问题的评论中提到的,您可以轻松地将您的键映射到 指针数组,例如:

    char *arr[] = { "A", "A#", "B", "Bb", "C", "C#", "D", /* array of pointers */
                    "D#", "E", "F", "F#", "G", "G#" };

然后,您可以将值保存在具有相同数量元素的单独整数数组中,其中键 "A"(例如 0)的索引对应于值数组中的关联值(例如 values[0] = 10;)。这提供了"A"10 的简单映射。

然后给定您的指针数组和'key',然后您可以遍历您的数组,尝试将key 与每个字符串匹配。当在 index 找到匹配项时,您的关联键是 values[index];

将它们放在一个简单的函数中,该函数循环遍历指针数组(下面的s)中的每个'n' 字符串以找到提供的key 并在成功时返回索引,或者-1 如果键-未找到,您可以执行以下操作:

/* locate index in 's' associated with 'key'.
 * for 'n' keys in 's' return 'index' of matching 'key',
 * otherwise return -1 if key not found.
 */ 
int getvalue (char **s, char *key, int n)
{
    for (int i = 0; i < n; i++) {           /* loop over all keys */
        int found = 1;                      /* flag for key found */
        for (int j = 0; key[j]; j++) {      /* loop over all chars in key */
            if (key[j] != s[i][j]) {        /* if key doesn't match s[i] */
                found = 0;                  /* set not found, break */
                break;
            }
        }
        if (found)          /* if all chars in key match s[i] */
            return i;       /* return index of matching key */
    }
    return -1;
}

(注意:你可以用strcmp替换内循环,但不知道你是否有sting.h中的函数可用,一个简单的循环key中的字符就可以了每次。但是,如果您确实有string.h 可用,那么strcmp 将比滚动您自己的要好得多。您甚至可以使用strncmp (key, s[i], KEYSIZE) 将大缓冲区中的比较限制为仅感兴趣的字符)

将所有部分放在一个简短的示例中,该示例简单地将值随机分配给0 -&gt; No. keys - 1 数量范围内的每个键,并让用户输入一个键以获取相关值直到@987654346输入@ 或用户使用手动生成的EOF 取消输入(例如Linux 上的Ctrl+d 或windoze 上的Ctrl+z - 如果启用了旧模式),您可以执行以下操作:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

enum { KEYSZ = 2, BUFSZ = 256 };    /* if you need a constant, define it */

/* simply print of all mappings */
void prnmapping (char **s, int *a, int n)
{
    for (int i = 0; i < n; i++)
        printf ("  %-2s : %2d\n", s[i], a[i]);
}

/* locate index in 's' associated with 'key'.
 * for 'n' keys in 's' return 'index' of matching 'key',
 * otherwise return -1 if key not found.
 */ 
int getvalue (char **s, char *key, int n)
{
    for (int i = 0; i < n; i++) {           /* loop over all keys */
        int found = 1;                      /* flag for key found */
        for (int j = 0; key[j]; j++) {      /* loop over all chars in key */
            if (key[j] != s[i][j]) {        /* if key doesn't match s[i] */
                found = 0;                  /* set not found, break */
                break;
            }
        }
        if (found)          /* if all chars in key match s[i] */
            return i;       /* return index of matching key */
    }
    return -1;
}

int main (void) {

    char *arr[] = { "A", "A#", "B", "Bb", "C", "C#", "D", /*array of pointers*/
                    "D#", "E", "F", "F#", "G", "G#" };
    int nelem = sizeof arr / sizeof *arr,           /* number of elements */
        values[nelem];                              /* VLA for values */

    srand (time (NULL));                /* initialize random seed */

    for (int i = 0; i < nelem; i++)     /* initialize values array */
        values[i] = rand() % nelem;

    printf ("initial string mappings:\n");  /* just dump initial mappings */
    prnmapping (arr, values, nelem);
    putchar ('\n');

    for (;;) {  /* loop continually prompting for key input until 'quit' */
        char buf[BUFSZ] = "",       /* buffer for line */
            key[KEYSZ + 1] = "";    /* buffer for key (can just use buf) */
        int index = 0;              /* int to hold index matching key */
        size_t len = 0;             /* length of input string */
        printf ("enter key ('quit' to exit): ");    /* prompt */
        if (fgets (buf, BUFSZ, stdin) == NULL) {    /* catch manual EOF */
            printf ("user canceled input.\n");
            break;
        }
        if ((len = strlen (buf)) <= 1) {    /* continue if empty line */
            fprintf (stderr, "error: insufficient input.\n");
            continue;
        }
        if (buf[len - 1] == '\n')   /* check buf for trailing '\n' */
            buf[--len] = 0;         /* overwrite with nul-terminating char */
        else {  /* otherwise input equals or exceeds buffer length */
            fprintf (stderr, "error: input exceeds %d chars.\n", BUFSZ - 2);
            break;
        }
        if (strcmp (buf, "quit") == 0)  /* compare for 'quit' */
            break;
        strncpy (key, buf, KEYSZ);  /* copy KEYSZ chars from buf to key */
        key[len] = 0;  /* nul-terminate key (already done by initialization) */
        if ((index = getvalue (arr, key, nelem)) == -1) /* key not found */
            fprintf (stderr, "error: key not found.\n");
        else    /* success - key found, output associated value */
            printf ("  key: '%s'  -  value: %d\n", key, values[index]);
    }

    return 0;
}

(注意: 提供了合理的错误处理。无论您使用哪种方式获取用户输入(尽管我们鼓励您使用 fgets 或 POSIX getline 而不是 scanf对于scanf) 中的许多陷阱,您必须验证所有输入。这意味着检查您使用的任何输入函数的返回——至少,并通过生成EOF 来处理用户取消输入)

使用/输出示例

$ ./bin/array_map_values
initial string mappings:
  A  :  4
  A# :  4
  B  :  1
  Bb :  7
  C  :  1
  C# : 11
  D  :  8
  D# :  3
  E  :  4
  F  :  2
  F# :  6
  G  :  8
  G# : 10

enter key ('quit' to exit): A
  key: 'A'  -  value: 4
enter key ('quit' to exit): A#
  key: 'A#'  -  value: 4
enter key ('quit' to exit): D
  key: 'D'  -  value: 8
enter key ('quit' to exit): D#
  key: 'D#'  -  value: 3
enter key ('quit' to exit): G#
  key: 'G#'  -  value: 10
enter key ('quit' to exit): g@
error: key not found.
enter key ('quit' to exit): B
  key: 'B'  -  value: 1
enter key ('quit' to exit): quit

查看一下,如果您还有其他问题,请告诉我。正如我在开始时所说,您可以在此练习中包含大量学习内容。

【讨论】:

  • 非常感谢!这很有帮助
  • 很好,我很乐意提供帮助。一旦你看到它放在一起,它就会帮助很多失败的目标落到实处。祝你编码顺利。
【解决方案4】:

以最简单的方式,您可以只保留两个平行的值数组来帮助将字符串映射到整数,如下所示:

const char *keys[13];
int keytoindex[13];

/* build table */
keys[0] = "A";
keys[1] = "A#";
/* etc. */

keytoindex[0] = 9; /* Value for 'A' */
keytoindex[1] = 10;
/* etc. */

/* later */
char *userInput = ...; /* somehow get input */
int keyvalue = -1; /* I assume -1 is not in the value array, so it can be an error condition that we didn't find that key when searching in the first array */
for( int i = 0; i < 13; i++ ) {
     if(strcmp(userInput,keys[i]) == 0 )) {
          keyvalue = keytoindex[i];
          break;
     }
}

if(keyvalue == -1) exit(1); /* send an error */
/* otherwise, keep going */

当然,可以重写此代码,以便动态分配并行数组,这将允许您在运行时调整它们的大小。但概念是一样的。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-03-22
    • 2012-03-20
    • 1970-01-01
    • 2015-12-31
    • 2020-06-19
    • 1970-01-01
    • 1970-01-01
    • 2017-10-10
    相关资源
    最近更新 更多