【问题标题】:fast string search to find string array element which matches the givven pattern快速字符串搜索以查找与给定模式匹配的字符串数组元素
【发布时间】:2014-10-06 12:44:25
【问题描述】:

我有一个常量字符串数组,我遍历它以查找元素的索引,该索引是包含搜索模式的字符串。我应该选择哪种搜索算法来提高查找此元素的速度?如果有必要,我在运行应用程序以准备查找表之前没有时间限制。

我更正了一个问题 - 我没有进行精确的字符串匹配 - 我正在搜索元素内的模式,该元素位于数组中

array of strings:
[0] Red fox jumps over the fence
[1] Blue table
[2] Red flowers on the fence

我需要找到一个包含单词“table”的元素 - 在本例中是它的元素 1

我确实喜欢一组 30 个数组的 50000 次迭代,其中最多可以包含 30000 个不少于 128 个字符的字符串。现在我正在使用旧的 strstr 蛮力,这太慢了......

好的,发布我函数的一部分,第一个 strstr - 如果有任何出现,则在未切割的行数组中查找,然后进行粗略搜索。我知道我可以加快这部分的速度,但我没有对这种方法进行优化......

// sheets[i].buffer - contains a page of a text which is split into lines
// fullfunccall - is the pattern
// sheets[i].cells[k] - are the pointers to lines in a buffer

for( i=0; i<sheetcount; i++) {
  if( i!= currsheet && sheets[i].name && sheets[i].name[0] != '\0') {
    if( strstr(sheets[i].buffer, fullfunccall )) {
      usedexternally = 1;

      int foundatleastone = 0;
      for( k=0; k<sheets[i].numcells; k++ ) {
        strncpy_s(testline, MAX_LINE_SIZE, sheets[i].cells[k]->line, sheets[i].cells[k]->linesize);
        testline[sheets[i].cells[k]->linesize] = '\0';

        if( strstr(testline, fullfunccall )) {
          dependency_num++;

          if( dependency_num >= MAX_CELL_DEPENDENCIES-1) {
            printf("allocation for sheet cell dependencies is insuficcient\n");
            return;
          }

          sheets[currsheet].cells[currcellposinsheet]->numdeps = dependency_num+1;
          foundatleastone++;
          sheets[currsheet].cells[currcellposinsheet]->deps[dependency_num] = &sheets[i].cells[k];
        }
      }

      if( foundatleastone == 0 ) {
        printf("error locating dependency for external func: %s\n", fullfunccall );
        return;
      }
    }
  };
}

【问题讨论】:

  • @DoomProg 30000 个元素,大约 200 个字符串
  • 您的要求并不完全清楚。您能否举例说明您正在寻找什么?
  • @MOehm 我编辑了包含示例的问题
  • 您可以对每个 array[i] 使用 strtok() 并使用 srtcmp() 在其中搜索您想要的单词。我没有看到另一种方式。你可以看到这个:cplusplus.com/reference/cstring/strtok
  • @DoomProg 谢谢,我已经这样做了,它的SLOW

标签: c fastsearch


【解决方案1】:

您写道,您的“干草堆”(要搜索的字符串集)大约有 30000 个字符串,其中大约有 30000 个字符串。每个 200 个字符。您还写道,“needle”(要搜索的术语)是 5 或 20 个字符的字符串。

基于此,您可以预先计算一个哈希表,它将任何 5 个字符的子序列映射到它所在的大海捞针中的字符串。对于 30000 个字符串(每个 200 个字符),最多有 30000 * (200 - 5 ) = 5.850.000 个不同的 5 字符子字符串。如果您将其中的每一个哈希为 16 位校验和,则您需要至少 11MB 的内存(用于哈希键)以及一些指向子字符串出现的字符串的指针。

例如,给定一个简化的干草堆

static const char *haystack[] = { "12345", "123456", "23456", "345678" };

您预先计算一个哈希映射,它映射任何可能的 5 个字符的字符串,例如

12345 => haystack[0], haystack[1]
23456 => haystack[1], haystack[2]
34567 => haystack[3]
45678 => haystack[4]

这样,您可以获取给定键的前五个字符(5 或 20 个字符长),对其进行哈希处理,然后通过哈希映射键所映射到的所有字符串执行普通的strstr

【讨论】:

  • 我一定会试试的
【解决方案2】:

对于您正在处理的每张工作表,您可以按照this article 中的描述构建一个后缀数组。在开始搜索之前,请阅读工作表,找到行首(作为工作表缓冲区的整数索引),创建后缀数组并按照文章中的说明对其进行排序。

现在,如果您要查找模式(例如“table”)出现的行,您可以搜索“table”之后的下一个条目和“tablf”之后的下一个条目,这是第一个非匹配,您移动了最右边的字母,里程表样式。

如果两个索引相同,则没有匹配项。如果它们不同,您将获得工作表中的指针列表:

"tab. And now ..."
----------------------------------------------------------------
"table and ..."                0x0100ab30
"table water for ..."          0x0100132b
"tablet computer ..."          0x01000208
----------------------------------------------------------------
"tabloid reporter ..."

这将为您提供一个指针列表,通过减去工作表缓冲区的基指针,您可以获得整数偏移量。与行开头的比较将为您提供与这些指针相对应的行号。 (行号已排序,因此您可以在此处进行二进制搜索。)

内存开销是一个与工作表缓冲区大小相同的指针数组,因此对于 30,000 个 200 个字符的字符串,在 64 位机器上大约为 48MB。 (行索引的开销可以忽略不计。)

对数组进行排序需要很长时间,但每张表只进行一次。

编辑:这个想法似乎运作良好。我已经实现了它,可以在不到一秒的时间内扫描text file of nearly 600k 上大约 130,000 个单词的字典:

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

#define die(...) exit((fprintf(stderr, "Fatal: " __VA_ARGS__), \
    putc(10, stderr), 1))



typedef struct Sheet Sheet;    

struct Sheet {
    size_t size;    /* Number of chars */
    char *buf;      /* Null-terminated char buffer */
    char **ptr;     /* Pointers into char buffer */
    size_t nline;   /* number of lines */
    int *line;      /* array of offset of line beginnings */
    size_t naux;    /* size of scratch array */
    char **aux;     /* scratch array */
};


/*
 *      Count occurrence of c in zero-terminated string p.
 */
size_t strcount(const char *p, int c)
{
    size_t n = 0;

    for (;;) {
        p = strchr(p, c);
        if (p == NULL) return n;
        p++;
        n++;        
    }

    return 0;
}

/*
 *      String comparison via pointers to strings.
 */
int pstrcmp(const void *a, const void *b)
{
    const char *const *aa = a;
    const char *const *bb = b;

    return strcmp(*aa, *bb);
}

/*
 *      Pointer comparison.
 */
int ptrcmp(const void *a, const void *b)
{
    const char *const *aa = a;
    const char *const *bb = b;

    if (*aa == *bb) return 0;   
    return (*aa < *bb) ? -1 : 1;
}

/*
 *      Create and prepare a sheet, i.e. a text file to search.
 */
Sheet *sheet_new(const char *fn)
{
    Sheet *sheet;
    FILE *f = fopen(fn, "r");
    size_t n;
    int last;
    char *p;
    char **pp;

    if (f == NULL) die("Couldn't open %s", fn);

    sheet = malloc(sizeof(*sheet));
    if (sheet == NULL) die("Allocation failed");

    fseek(f, 0, SEEK_END);
    sheet->size = ftell(f);
    fseek(f, 0, SEEK_SET);

    sheet->buf = malloc(sheet->size + 1);
    sheet->ptr = malloc(sheet->size * sizeof(*sheet->ptr));

    if (sheet->buf == NULL) die("Allocation failed");
    if (sheet->ptr == NULL) die("Allocation failed");

    fread(sheet->buf, 1, sheet->size, f);
    sheet->buf[sheet->size] = '\0';
    fclose(f);

    sheet->nline = strcount(sheet->buf, '\n');
    sheet->line = malloc(sheet->nline * sizeof(*sheet->line));

    sheet->aux = NULL;
    sheet->naux = 0;

    n = 0;
    last = 0;
    p = sheet->buf;
    pp = sheet->ptr;
    while (*p) {
        *pp++ = p;
        if (*p == '\n') {
            sheet->line[n++] = last;
            last = p - sheet->buf + 1;
        }
        p++;
    }

    qsort(sheet->ptr, sheet->size, sizeof(*sheet->ptr), pstrcmp);

    return sheet;
}

/*
 *      Clean up sheet.
 */
void sheet_delete(Sheet *sheet)
{
    free(sheet->buf);
    free(sheet->ptr);
    free(sheet->line);
    free(sheet->aux);
    free(sheet);
}

/*
 *      Binary range search for string pointers.
 */
static char **pstr_bsearch(const char *key,
    char **arr, size_t high)
{
    size_t low = 0;

    while (low < high) {
        size_t mid = (low + high) / 2;
        int diff = strcmp(key, arr[mid]);

        if (diff < 0) high = mid;
        else low = mid + 1;
    }

    return arr + low;
}

/*
 *      Binary range search for line offsets.
 */
static const int *int_bsearch(int key, const int *arr, size_t high)
{
    size_t low = 0;

    while (low < high) {
        size_t mid = (low + high) / 2;
        int diff = key - arr[mid];

        if (diff < 0) high = mid;
        else low = mid + 1;
    }

    if (low < 1) return NULL;
    return arr + low - 1;
}

/*
 *      Find occurrences of the string key in the sheet. Returns the
 *      number of lines in which the key occurs and assigns up to
 *      max lines to the line array. (If max is 0, line may be NULL.)
 */
int sheet_find(Sheet *sheet, char *key,
    int line[], int max)
{
    char **begin, **end;
    int n = 0;
    size_t i, m;
    size_t last;

    begin = pstr_bsearch(key, sheet->ptr, sheet->size);
    if (begin == NULL) return 0;

    key[strlen(key) - 1]++;
    end = pstr_bsearch(key, sheet->ptr, sheet->size);
    key[strlen(key) - 1]--;
    if (end == NULL) return 0;
    if (end == begin) return 0;

    m = end - begin;
    if (m > sheet->naux) {
        if (sheet->naux == 0) sheet->naux = 0x100;
        while (sheet->naux < m) sheet->naux *= 2;
        sheet->aux = realloc(sheet->aux, sheet->naux * sizeof(*sheet->aux));
        if (sheet->aux == NULL) die("Re-allocation failed");        
    }

    memcpy(sheet->aux, begin, m * sizeof(*begin));
    qsort(sheet->aux, m, sizeof(*begin), ptrcmp);

    last = 0;
    for (i = 0; i < m; i++) {
        int offset = sheet->aux[i] - sheet->buf;
        const int *p;

        p = int_bsearch(offset, sheet->line + last, sheet->nline - last);

        if (p) {
            if (n < max) line[n] = p - sheet->line;
            last = p - sheet->line + 1;
            n++;
        }
    }

    return n;
}

/*
 *      Example client code
 */
int main(int argc, char **argv)
{
    Sheet *sheet;
    FILE *f;

    if (argc != 3) die("Usage: %s patterns corpus", *argv);

    sheet = sheet_new(argv[2]);

    f = fopen(argv[1], "r");
    if (f == NULL) die("Can't open %s.", argv[1]);
    for (;;) {
        char str[80];
        int line[50];
        int i, n;

        if (fgets(str, sizeof(str), f) == NULL) break;
        strtok(str, "\n");
        n = sheet_find(sheet, str, line, 50);
        printf("%8d %s\n", n, str);

        if (n > 50) n = 50;
        for (i = 0; i < n; i++) printf("    [%d] %d\n", i, line[i] + 1);
    }
    fclose(f);

    sheet_delete(sheet);

    return 0;
}

该实现有其粗糙的边缘,但它确实有效。我不是特别喜欢临时数组和在找到的指针范围上的额外排序,但事实证明,即使对大后缀数组进行排序也不会花费太长时间。

如果您愿意,可以将此解决方案扩展到更多工作表。

【讨论】:

    【解决方案3】:

    我相信最实用的是 DFA,因为它最多读取输入的每个字符一次 - 更准确地说,它读取每个输入字符一次并在模式不完全匹配时立即停止(如果设置正确)。使用 DFA,您还可以同时检查多个模式。

    在实践中经过良好测试的 DFA 算法的两个良好(但不同)实现是

    除非您提供更多相关信息,否则无法说出哪个适合您的任务。

    编辑:DFA 保留用于“确定性有限自动机”

    编辑:正如您所说,您的模式是精确的子字符串,最常见的解决方案是 KMP 算法(Knuth-Morris-Pratt)

    【讨论】:

    • 我确实喜欢一组 30 个数组的 50000 次迭代,其中最多可以包含 30000 个不少于 128 个字符的字符串
    • 如果 50000 次迭代意味着 50000 个静态搜索模式,那么 DFA 绝对是赢家,因为您可以将许多模式合并到一次迭代中。 PIRE 专门为此而设计,而 Ragel 也可以在您的帮助下处理此问题(即以其输入语言生成源代码)
    • 谢谢彼得,但我希望这项工作有 3 或 4 个函数,而不是库,但我会看看
    • DFA 是要走的路;一旦构建,它只需要输入文件一次。这就像大海捞针。
    • Ragel 是一个程序(不是库),它会在 C 语言中为您生成一个您可以链接的函数
    猜你喜欢
    • 1970-01-01
    • 2014-02-06
    • 1970-01-01
    • 1970-01-01
    • 2018-04-09
    • 1970-01-01
    • 1970-01-01
    • 2013-01-20
    • 1970-01-01
    相关资源
    最近更新 更多