【问题标题】:C dictionary/mapC 字典/地图
【发布时间】:2011-05-11 21:09:58
【问题描述】:

我想映射结构成员,这样我就可以消除循环中的 if 分支。在 C 中实现这一点的最佳方式或约定是什么?我想它可能是一个二维数组...然后我可以将整数映射到字符键?

    char chunk[32];
    int n;
    int i;
    char *ptr = config;
    while (*ptr != '\0') {
        int items_read = sscanf(ptr, "%31[^;]%n", chunk, &n);

        if(chunk[0] == 'S' && chunk[1] == 'P') {
            for(i=0;i<GLOBAL_MEAS_CUTOFF; i++) {
                theMeas[i].signal_path = atoi(&chunk[2]);
            }
        }    
        if(chunk[0] == 'T' && chunk[1] == 'L') {
            for(i=0;i<GLOBAL_MEAS_CUTOFF; i++) {
                theMeas[i].trace_length = atoi(&chunk[2]);
            }
        }    
        if(chunk[0] == 'S' && chunk[1] == 'R') {
            for(i=0;i<GLOBAL_MEAS_CUTOFF; i++) {
                theMeas[i].sample_rate = atoi(&chunk[2]);
            }
        }   

        chunk[0]='\0';
        if (items_read == 1)
            ptr += n;
        if ( *ptr != ';' ) {
            break;
        }
        ++ptr;
    }

【问题讨论】:

  • 你想要像 Python 这样的字典,还是想要更简单的方法来访问 struct 中的命名数据?一个比另一个难。
  • 我想删除 if 分支。所以我可以在循环中有一个语句,其中块映射到结构成员。在像 python 这样的动态语言中,我可以做 theMeas[i][chunk] = chunk[2].
  • chunk 看起来你不能“只”做theMeas[i][chunk](即使在 Python 中)。您将前两个字符作为您想要的成员的首字母缩写词。您只能在查找中使用 chunk[0]chunk[1]
  • @Chris 是的,我意识到这一点,我很马虎,所以你明白了基本的想法。我想没有简单的方法可以做到这一点。

标签: c struct hashmap hashtable


【解决方案1】:

我怀疑你(理想情况下)想要的是一本字典:

theMeas[i]["signal_path"] = atoi(&chunk[2]);

当然,上面的语法在 C 中永远不会发生,但这在这里并不重要。问题是您必须编写实现字典数据类型的所有代码,我怀疑这有点过头了。

所以我怀疑你(真正)想要的是一种可以在循环中使用的名称:

foreach(signal_path, trace_length, sample_rate)

我在这里告诉你,你可以做到这一点(有点)!最简单的方法是使用enum

enum fields {
  signal_path,
  trace_length,
  sample_rate,
  END_fields,
  UNKNOWN_fields,
  BEGIN_fields = 0,
};

您使用数组而不是 struct 成员:

int theMeas[size][END_fields];

要索引“成员”,请使用:

theMeas[i][signal_path];

你可以遍历所有的“成员”,你可以这样使用:

for(enum fields j = BEGIN_fields; j != END_fields; j++)
    theMeas[i][j];

当您想要获得基于字符的比较时,这确实会有点问题,但我们可以做一点:

const char *to_str(enum fields f)
{
#define FIELD(x) case x: return #x
    switch(f)
      {
        FIELD(signal_path);
        FIELD(trace_length);
        FIELD(sample_rate);
        default: return "<unknown>";
      }
#undef FIELD
}

enum fields from_str(const char *c)
{
#define FIELD(x) if(!strcmp(c, #x)) return x
        FIELD(signal_path);
        FIELD(trace_length);
        FIELD(sample_rate);
        default: return UNKNOWN_fields;
#undef FIELD
}

enum fields from_abv(char *c)
{
    for(enum fields i = BEGIN_fields; i < END_fields; i++)
      {
        char *field = field_str(i);
        if(tolower(c[0]) == field[0] && tolower(c[1]) == strchr(field, '_')[1])
            return i;
      }
    return UNKNOWN_fields;
}

您的if 语句可以替换为:

theMeas[i][from_abv(chunk)] = atoi(&chunk[2]);

或者,更安全:

enum fields j = from_abv(chunk);
if(j != UNKNOWN_fields) theMeas[i][j] = atoi(&chunk[2]);
else /* erroneous user input */;

这是我能得到的最接近的地方。

请注意,我特意使用了一种命名方案来促进宏的创建,这些宏将自动完成大部分工作。让我们试试吧:

#define member(name, ...) \
  enum name { __VA_ARGS__, \
              M_END_##name, \
              M_UNKNOWN_##name, \
              M_BEGIN_##name = 0 }

#define miter(name, var) \
        enum name var = M_BEGIN_##name; var != M_END_##name; var++

#define msize(name) M_END_##name

用法:

// define our fields
member(fields, signal_path, trace_length, sample_rate);

// declare object with fields
int theMeas[N][msize(fields)];

for(size_t i = 0; i < N; i++)
    // iterate over fields
    for(miter(fields, j))
        // match against fields
        if(j == from_abv(chunk))
            theMeas[i][j] = atoi(&chunk[2]);

最后一点似乎还不错。它仍然允许您通过theMeas[i][signal_path] 访问类似于struct 的东西,但允许您迭代“成员”,并将大部分繁重的工作隐藏在宏后面。

to_strfrom_str 函数需要更多的宏技巧来自动化。您可能需要为此查看 P99。对于一般情况,我不建议使用 from_abv 函数,因为我们无法保证下次创建可迭代字段时将使用带下划线的名称。 (当然,您可以删除 from_abv 函数,并为您的成员提供难以理解的名称,例如 SPTLSR,让您可以直接将它们与您的字符串数据进行比较,但您需要更改将strcmp 转换为memcmp,其大小参数为(sizeof(#x) - 1)。然后您拥有from_abv 的所有位置都只需使用from_str,它可以为您自动生成。)

但是,from_abv 并不难定义,老实说,您可以从上面复制并粘贴您的 if 块到其中 - 它会稍微更有效,但如果您添加一个“成员”,您'd 必须更新函数(如所写,如果您添加成员,它将自行更新。)

【讨论】:

  • 这就是我拖延论文时会发生的情况。
【解决方案2】:

C 支持pointers to functions,因此您可以创建一个指向函数的指针数组并根据您的输入寻址该数组。这将需要您实现具有相同签名的其他功能。

另一种方法可能是将 if 子句封装在一个单独的函数中并使用参数调用它。

但是,如果有的话,我认为这两种方式都不会让你获得太多的加速。

【讨论】:

  • 谢谢,不求速度,只是想减少代码中的重复
【解决方案3】:

你可以用一个指向整数的指针来重写你的逻辑:

while (*ptr != '\0') {
    int items_read = sscanf(ptr, "%31[^;]%n", chunk, &n);

    int *p = NULL;
    if(chunk[0] == 'S' && chunk[1] == 'P') {
        p = &theMeas[i].signal_path;
    }    
    if(chunk[0] == 'T' && chunk[1] == 'L') {
        p = &theMeas[i].trace_length;
    }    
    if(chunk[0] == 'S' && chunk[1] == 'R') {
        p = &theMeas[i].sample_rate;
    }

    for(i=0;i<GLOBAL_MEAS_CUTOFF; i++) {
        *p = atoi(&chunk[2]);
    }   

这种方法将关于更改什么变量的决定(if 语句)与实际执行每种情况共同工作的代码(for 循环)分开。

您当然要检查p == NULL 是否以防chunk[0]chunk[1] 不符合您的预期。

【讨论】:

  • 啊,循环错误。我正在尝试将块 cmd 映射到所有 cmd 类型的结构成员....所以我可以删除 if 分支,并且循环中只有一个语句。
  • 或者使用if/else if/else if/else捕捉不匹配的情况。
  • @eat_a_lemon:抱歉,我没明白你真正想要什么。我不知道“将块 cmd 映射到结构成员”是什么意思。
  • 底线是我想要一个哈希表,我可以在其中将 2 个字符映射到结构中的一个字段。然而,用 C 语言做这件事似乎不是一件简单的事情,实现它并不能真正为我节省太多,所以我就让它保持原样。谢谢
【解决方案4】:

不幸的是,对于简单的 C99,这是不可能的,因为数组索引只能是无符号整数。但大概strncmp() 功能更适合你?

#define EQUALN(a,b,n) (strncmp(a, b, n) == 0)

...

if(EQUALN(chunk, "SP", 2)) {
    for(i=0;i<GLOBAL_MEAS_CUTOFF; i++) {
        theMeas[i].signal_path = atoi(&chunk[2]);
    }
}    
else if(EQUALN(chunk, "TL", 2)) {
    for(i=0;i<GLOBAL_MEAS_CUTOFF; i++) {
        theMeas[i].trace_length = atoi(&chunk[2]);
    }
}    
else if(EQUALN(chunk, "SR", 2)) {
    for(i=0;i<GLOBAL_MEAS_CUTOFF; i++) {
        theMeas[i].sample_rate = atoi(&chunk[2]);
    }
}

【讨论】:

  • 嗨,谢谢,是的,if 语句仍然存在。我正在尝试删除它们。
【解决方案5】:

如果(这是一个相当大的 if)你可以依赖数据 always 作为这三个选项之一,那么我们可以在这三种情况下构造一个“最小完美哈希”。假设字符集为 ASCII(或与 ASCII 一致):

L = 76, 0 mod 4
P = 80, 0 mod 4
R = 82, 2 mod 4
S = 83, 3 mod 4
T = 84, 0 mod 4

所以,S+P 是 3 mod 4,T+L 是 0 mod 4,S+R 是 1 mod 4。不是最小,但足够接近:

size_t lookup[3] = {
    offsetof(Mea, trace_length),
    offsetof(Mea, sample_rate),
    0,
    offsetof(Mea, signal_path)
};

size_t offset = lookup[((unsigned)chunk[0] + chunk[1]) % 4];

for(i=0;i<GLOBAL_MEAS_CUTOFF; i++) {
    int *fieldptr = (int*)(((char*)(theMeas+i)) + offset);
    *fieldptr = atoi(&chunk[2]);
}

您可能更喜欢用宏或内联函数在这只猪身上涂上口红,或者用char *fieldptr 代替int *fieldptr,将它从((char*)theMeas) + offset 开始,然后每次递增sizeof(Mea)

如果您不能依赖友好的数据,那么您至少需要某种分支(条件或通过函数指针的调用),以避免在数据不好的情况下编写任何内容。即使将其保持为 1,您也可能需要针对 3 个案例的 64k 条目查找表,这有点稀疏,因此您可能最好使用条件。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-08-21
    • 1970-01-01
    • 1970-01-01
    • 2014-07-22
    • 2016-03-18
    • 1970-01-01
    • 2016-07-19
    相关资源
    最近更新 更多