快速C比较

时间:2011-07-10 21:18:30

标签: c comparison substring

作为协议的一部分,我收到以下格式的C字符串:
WORD * WORD
两个WORD都是相同的字符串。
并且,* - 是任何可打印字符串,不包括空格!

所以以下都是合法的:

  • WORD asjdfnkn WORD
  • WORD 234kjk2nd32jk WORD

以下是非法的:

  1. WORD akldmWORD
  2. WORD asdm zz WORD
  3. NOTWORD admkas WORD
  4. NOTWORD admkas NOTWORD
  5. 其中(1)缺少尾随空格; (2)有3个或更多的空格; (3)/(4)不要用正确的字符串(WORD)打开/结束。

    当然,这可以非常简单地实施,但是我不确定我所做的是最有效的。 注意:WORD是为整个运行预先设置的,但是可以在运行之间进行更改。

    目前我正在针对“WORD”强调每个字符串。 如果手动检查(char-by-char)在字符串上运行,则检查第二个空格char [如果找到]我然后用“WORD”strcmp(一路)。

    我很乐意听取您的解决方案,并强调效率,因为我将实时运行数百万篇论文。

9 个答案:

答案 0 :(得分:4)

我会说,看看Handbook of Exact String-Matching Algorithms中的算法,比较复杂性并选择你最喜欢的算法,然后实现它。

或者你可以使用一些现成的实现。

你有一些非常经典的算法来搜索另一个字符串中的字符串:

KMP(Knuth-Morris-Pratt)

Rabin-Karp

Boyer-Moore

希望这会有所帮助:)

答案 1 :(得分:2)

可能需要在最短代码和最快实现之间进行权衡。选择是:

  1. 正则表达式^WORD \S+ WORD$(需要正则表达式引擎)

  2. strchr"WORD "上,strrchr在“WORD”上有很多杂乱的支票(不是真的推荐)

  3. 按字符逐行处理整个字符串,跟踪您所处的状态(扫描第一个字,扫描第一个空格,扫描中间空格,扫描最后一个空格,扫描最后一个字,预期字符串结束)。

  4. 选项1需要最少的代码,但在结尾附近回溯,而选项2没有兑换质量。我认为你可以优雅地做选项3。使用状态变量,看起来没问题。请记住根据单词的长度和整个字符串的长度手动输入最后两个状态,这样可以避免正则表达式最有可能产生的回溯。

答案 2 :(得分:2)

你有资格吗?

这里没有多少收获,因为你正在进行基本的字符串比较。如果您想获得性能的最后几个百分点,我会更改str...函数的mem...函数。

char *bufp, *bufe; // pointer to buffer, one past end of buffer
if (bufe - bufp < wordlen * 2 + 2)
    error();
if (memcmp(bufp, word, wordlen) || bufp[wordlen] != ' ')
    error();
bufp += wordlen + 1;
char *datap = bufp;
char *datae = memchr(bufp, ' ', bufe - buf);
if (!datae || bufe - datae < wordlen + 1)
    error();
if (memcmp(datae + 1, word, wordlen))
    error();
// Your data is in the range [datap, datae).

性能提升可能不那么引人注目。您必须检查缓冲区中的每个字符,因为每个字符可能是一个空格,并且分隔符中的任何字符都可能错误。将循环更改为memchr是光滑的,但现代编译器知道如何为您执行此操作。将strncmpstrcmp更改为memcmp也可能是微不足道的。

答案 3 :(得分:1)

你知道要检查的字符串有多长?如果没有,你的能力有限。如果你知道字符串有多长,你可以加快速度。您尚未指定'*'部分必须至少包含一个字符。您还没有规定是否允许制表符,换行符,或者......是否只是字母数字(如您的示例中所示)或者是否允许使用标点符号和其他字符?控制字符?

你知道WORD有多长,并且可以预先构建开始和结束标记。函数error()报告错误(但是您需要报告它)并返回false。测试函数可能为bool string_is_ok(const char *string, int actstrlen);,成功时返回true,出现问题时返回false

// Preset variables characterizing the search
static int  wordlen    = 4;
static int  marklen    = wordlen + 1;
static int  minstrlen  = 2 * marklen + 1;  // Two blanks and one other character.
static char bword[]    = "WORD ";          // Start marker
static char eword[]    = " WORD";          // End marker
static char verboten[] = " ";              // Forbidden characters

bool string_is_ok(const char *string, int  actstrlen)
{
    if (actstrlen < minstrlen)
        return error("string too short");
    if (strncmp(string, bword, marklen) != 0)
        return error("string does not start with WORD");
    if (strcmp(string + actstrlen - marklen, eword) != 0)
        return error("string does not finish with WORD");
    if (strcspn(string + marklen, verboten) != actstrlen - 2 * marklen)
        return error("string contains verboten characters");
    return true;
}

如果您需要保证,可能无法减少测试。根据字母表中的限制而变化最大的部分是strcspn()行。一小部分禁用字符的速度相对较快;随着禁止的字符数增加,它可能会变慢。如果你只允许使用字母数字,那么除非你将一些高位设置字符计为字母,否则你有62个OK和193个不正常的字符。那部分可能会很慢。使用自定义函数可以做得更好,该函数采用起始位置和长度并报告所有字符是否正常。这可能是:

#include <stdbool.h>

static bool ok_chars[256] = { false };

static void init_ok_chars(void)
{
    const unsigned char *ok = "abcdefghijklmnopqrstuvwxyz...0123456789";
    int c;
    while ((c = *ok++) != 0)
        ok_chars[c] = 1;
}

static bool all_chars_ok(const char *check, int numchars)
{
    for (i = 0; i < numchars; i++)
        if (ok_chars[check[i]] == 0)
            return false;
    return true;
}

然后您可以使用:

return all_chars_ok(string + marklen, actstrlen - 2 * marklen);

代替对strcspn()的调用。

答案 4 :(得分:1)

如果你的“填充”应该只包含'0' - '9','A' - 'Z'和'a' - 'z'并且是基于ASCII的某种编码(就像大多数基于Unicode的编码一样),然后你可以在你的一个循环中跳过两个比较,因为大写字符和次要字符之间只有一位不同。 而不是

   ch>='0' && ch<='9' && ch>='A' && ch<='Z' && ch>='a' && ch<='a'

你得到了

   ch2 = ch & ~('a' ^ 'A')

   ch>='0' && ch<='9' && ch2>='A' && ch2<='Z'

但是你最好看看你的编译器生成的汇编代码并做一些基准测试,这取决于计算机架构和编译器,这个技巧可能会让代码变慢。

如果与计算机上的比较相比,分支费用昂贵,您也可以将&&替换为&。但大多数现代编译器在大多数情况下都知道这个技巧。

另一方面,如果您测试某些大字符编码中的任何可打印字形,那么测试空白字形而不是可打印字形的可能性更低。

此外,专门针对运行代码的计算机进行编译,并且不要忘记转换任何一代的调试代码。

加了:

除非值得,否则不要在扫描循环中进行子程序调用。

无论你使用什么技巧来加速循环,如果你必须在其中一个循环中进行子程序调用,它就会减少。使用编译器内嵌到代码中的内置函数是很好的,但是如果你使用lika一个外部正则表达式库并且编译器无法内联这些函数(gcc可以这样做,有时,如果你要求它),然后进行该子程序调用将改变大量内存,更糟糕的是在不同类型的内存(寄存器,CPU缓冲区,RAM,硬盘等)之间,并可能搞乱CPU预测和管道。除非你的文本片段很长,所以你花了很多时间来解析它们,并且子程序足以有效地补偿调用的成本,不要这样做。一些用于解析的函数使用回调,它可能比你从循环中进行大量子程序调用更有效(因为该函数可以在一次扫描中扫描几个模式匹配并在关键循环之外将几个回调串起来) ,但这取决于其他人如何编写该功能,基本上与您拨打电话是一回事。

答案 5 :(得分:0)

WORD是4个字符,使用uint32_t可以快速比较。根据系统字节顺序,您将需要不同的常量。其余的似乎很好。

由于WORD可以改变,你必须预先计算uint32_t,uint64_t,......你需要根据WORD的长度。

从描述中不确定,但如果你信任来源,你可以选择前n + 1和最后n + 1个字符。

答案 6 :(得分:0)

bool check_legal(
        const char *start, const char *end,
        const char *delim_start, const char *delim_end,
        const char **content_start, const char **content_end
) {
  const size_t delim_len = delim_end - delim_start;
  const char *p = start;

  if (start + delim_len + 1 + 0 + 1 + delim_len < end)
    return false;

  if (memcmp(p, delim_start, delim_len) != 0)
    return false;
  p += delim_len;

  if (*p != ' ')
    return false;
  p++;

  *content_start = p;
  while (p < end - 1 - delim_len && *p != ' ')
    p++;
  if (p + 1 + delim_len != end)
    return false;
  *content_end = p;
  p++;

  if (memcmp(p, delim_start, delim_len) != 0)
    return false;

  return true;
}

以下是如何使用它:

const char *line = "who is who";
const char *delim = "who";
const char *start, *end;

if (check_legal(line, line + strlen(line), delim, delim + strlen(delim), &start, &end)) {
  printf("this %*s nice\n", (int) (end - start), start);
}

(这都是未经测试的。)

答案 7 :(得分:0)

这应该在O(n)时间内返回真/假条件

int sameWord(char *str)
{
  char *word1, *word2;
  word1 = word2 = str;

  // Word1, Word2 points to beginning of line where the first word is found

  while (*word2 && *word2 != ' ') ++word2; // skip to first space
  if (*word2 == ' ') ++word2; // skip space

  // Word1 points to first word, word2 points to the middle-filler

  while (*word2 && *word2 != ' ') ++word2; // skip to second space
  if (*word2 == ' ') ++word2; // skip space

  // Word1 points to first word, word2 points to the second word

  // Now just compare that word1 and word2 point to identical strings.

  while (*word1 != ' ' && *word2)
     if (*word1++ != *word2++) return 0; //false
  return *word1 == ' ' && (*word2 == 0 || *word2 == ' ');
}

答案 8 :(得分:0)

使用STL找到空格的数量..如果它们不是两个,显然字符串是错误的......并且使用find(algorithm.h)你可以得到两个空格和中间字的位置!检查开头和结尾的WORD!你做完了..