【问题标题】:a better than naive implementation of strcspn()比 strcspn() 的简单实现更好
【发布时间】:2011-11-21 20:29:50
【问题描述】:

我必须弄清楚我的主题字符串是否有任何坏字符(我绝对讨厌某些字符)。因此,如果我有一个名为 str(char *str) 的字符串,并且如果找到字符串 bad(char *bad) 中的任何字符,则字符串str 将被拒绝。现在我可以使用strcspn(str,bad) 来检查这一点。但是有人可以建议strcspn 的实现是什么吗? 一个简单的实现是检查str 的每个字符与bad 的每个字符,如果找到匹配项则拒绝str

for(i=0;str[i]!='\0';i++)
  for(j=0;bad[j]!='\0';j++)
    if(bad[j]==str[i])
       return -1;   //reject string
return 1;    //accept string

或类似的东西

for(i=0;str[i]!='\0';i++)
  if(strchr(bad,str[i]))   //will return non-NULL if str[i] is found in bad
    return -1;   //reject string
return 1;    //accept string

【问题讨论】:

  • 请注意,第一个sn-p的内部for循环中的条件应该是bad[j]!='\0',而不是bad[j]='\0'
  • @Tamás 谢谢。刚刚错过了!更正了。

标签: c algorithm


【解决方案1】:

如果您关心最坏情况下的性能和/或线程安全,这里有一个将表保留在堆栈中的变体。

#include <limits.h>
#include <stddef.h>

int contains_bad(const char *str, const char *bad) {
  size_t hint[UCHAR_MAX];
  size_t len_bad;
  for (len_bad = 0; bad[len_bad]; len_bad++) {
    hint[(unsigned char)bad[len_bad] - 1] = len_bad;
  }
  for (; *str; str++) {
    size_t i = hint[(unsigned char)*str - 1];
    if (i < len_bad && *str == bad[i]) return 1;
  }
  return 0;
}

(对于语言律师:从技术上讲,未初始化的数组 hint 可能潜伏着陷阱表示。如果您知道这意味着什么并且真正关心 C 程序在早已死去的平台上的行为,一种解决方案,如果 bad是一个集合,因此不超过UCHAR_MAX 个字符,是将size_t hint[UCHAR_MAX] 更改为unsigned char hint[UCHAR_MAX],因为保证无符号字符没有陷阱表示。)

【讨论】:

    【解决方案2】:

    如果str 很长(或者您要针对同一组坏字符检查许多字符串),您可以通过创建一个大小为 256 的查找表来获得一些性能,其中元素 i 如果 ASCII 码 i 的字符不正确,则为 1,否则为零:

    int contains_bad(const char* str, const char* bad) {
        unsigned short int table[256];
        char* ch;
    
        /* Prepare the lookup table */
        memset(table, 0, 256);
        for (ch = bad; *ch != 0; ch++)
            table[*ch] = 1;
    
        /* Test the string */
        for (ch = str; *ch != 0; ch++)
            if (table[*ch])
                return -1;
    
        return 1;
    }
    

    上面的代码是O(m+n)最坏的情况,其中mbad的长度,nstr;你的解决方案是 O(mn) 最坏的情况。


    更新:这是该函数的替代版本,它将查找表保存在静态存储中,并且每 255 次调用仅清除一次。

    int contains_bad(const char* str, const char* bad) {
        static unsigned short int table[256];
        static unsigned short int marker = 255;
        char* ch;
    
        /* Prepare the lookup table */
        if (marker == 255) {
            memset(table, 0, 256);
            marker = 1;
        } else {
            marker++;
        }
        for (ch = bad; *ch != 0; ch++)
            table[*ch] = marker;
    
        /* Test the string */
        for (ch = str; *ch != 0; ch++)
             if (table[*ch] == marker)
                 return -1;
    
        return 1;
    }
    

    【讨论】:

    • 谢谢。当我在寻找strcspn 的实现时,我偶然发现了this link,但无法理解它。表驱动程序是否与您所说的相同
    • 是的,这是通常的表驱动方法。该链接上提到的实现使用了一个额外的变量,比如marker,它最初为 1,每次调用后都会增加 1。然后在表格中填充 marker 以表示错误字符,并在第二个 for 循环中与 marker 进行比较。当marker 达到 256(并溢出为零)时,将其设置为 1,并使用memset 清除表。因此,以一些额外的簿记为代价,您可以渐近地摆脱memset 的成本,但您必须使tablemarker 成为静态或线程本地的。
    • 但是你为什么需要marker?您可以将表格保存在静态存储中并设置一次(对于坏字符非零)。
    • 是的,但是如果第二次调用该函数并使用一组不同的坏字符怎么办?由于table 现在是静态的,其内容在调用之间保持不变,因此您已经在表中标记了上一次调用的坏字符,因此您需要一个 不同 标记来标记那些对应于当前调用。
    猜你喜欢
    • 2015-07-08
    • 2013-09-16
    • 1970-01-01
    • 2011-05-12
    • 2011-05-09
    • 2018-01-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多