通配符串搜索算法

时间:2011-08-15 15:45:38

标签: c++ string algorithm

在我的程序中,我需要搜索一个相当小的子串(<1 kb)的相当大的字符串(~1 mb)。 问题是字符串包含“a?c”意义上的简单通配符,这意味着我想搜索“abc”或“apc”等字符串,...(我只对第一次出现感兴趣)。 / p>

到目前为止,我使用了简单的方法(这里是伪代码)

algorithm "search", input: haystack(string), needle(string)

for(i = 0, i < length(haystack), ++i)
 if(!CompareMemory(haystack+i,needle,length(needle))
  return i;

return -1; (Not found)

其中“CompareMemory”返回0 iff第一个和第二个参数相同(也与通配符有关)仅与第三个参数给出的字节数有关。

我现在的问题是,如果有一个快速算法(你不必给它,但如果你这样做,我宁愿选择c ++,c或伪代码)。我开始了here  但我认为大多数快速算法都不允许使用通配符(顺便说一下它们利用字符串的性质)。

我希望问题的格式是正确的,因为我是新来的,请提前谢谢!

3 个答案:

答案 0 :(得分:3)

一种快速的方式,与使用正则表达式相同(我建议无论如何),是找到固定在针上的东西,“a”,但不是“?”,并搜索它,然后看看你是否完全匹配。

j = firstNonWildcardPos(needle)
for(i = j, i < length(haystack)-length(needle)+j, ++i)
  if(haystack[i] == needle[j])
    if(!CompareMemory(haystack+i-j,needle,length(needle))
      return i;

return -1; (Not found)

正则表达式会产生类似于此的代码(我相信)。

答案 1 :(得分:1)

在c字符的字母表中的字符串中,让S具有长度s并且让T_1 ... T_k具有平均长度b。将搜索S个目标字符串中的每一个。 (问题陈述没有提到对给定字符串的多次搜索;我在下面提到它,因为在该范例中我的程序运行良好。)

程序使用O(s + c)时间和空间进行设置,并且(如果S和T_i是随机字符串)O(k * u * s / c)+ O(k * b + k * b * s / c ^ u)搜索的总时间,如图所示在程序中u = 3。对于更长的目标,你应该增加,并选择罕见的,广泛分离的关键字符。

在步骤1中,程序创建一个s + TsizMax整数的数组L(在程序中,TsizMax =允许的目标长度)并将其用于下一次出现字符的位置的c列表,列表头在H []和T []中的尾巴。这是O(s + c)时间和空间步骤。

在步骤2中,程序重复读取并处理目标字符串。步骤2A选择u = 3个不同的非通配字符(在当前目标中)。如图所示,程序只使用前三个这样的字符;通过更多的工作,它可以改为使用目标中最稀有的字符来提高性能。请注意,它不能处理少于三个此类字符的目标。

线“L [T [r]] = L [g + i] = g + i;”在步骤2A中,在L中设置具有适当增量偏移的保护单元,使得步骤2G将在搜索结束时自动执行,而不需要在搜索期间进行任何额外测试。 T [r]为字符r索引列表的尾部单元格,因此单元格L [g + i]成为字符r的新的自引用列表结尾。 (这种技术允许循环以最少的无关条件测试运行。)

步骤2B将变量a,b,c设置为列表头部位置,并设置与目标中所选关键字符之间的距离对应的增量dab,dac和dbc。

步骤2C检查关键字符是否出现在S中。此步骤是必要的,因为否则步骤2E中的while循环将挂起。我们不希望在循环中进行更多检查,因为它们是搜索的内部循环。

步骤2D执行步骤2E到2i,直到var c指向S的结尾,此时不可能再进行匹配。

步骤2E由u = 3 while循环组成,“强制增量距离”,即,只要它们不是模式兼容的,就将爬行索引a,b,c相互叠加。 while循环相当快,每个实质上(删除了++ si仪器)“while(v + d&lt; w)v = L [v]”用于各种v,d,w。复制三个while循环几次可能会略微提高性能,并且不会改变净结果。

在步骤2G中,我们知道u个关键字符匹配,因此我们将目标与匹配点进行完全比较,并进行野性字符处理。步骤2H报告比较结果。所给出的计划也报告了本节中的不匹配;在生产中删除它。

步骤2I推进所有关键字符索引,因为当前索引字符都不是另一个匹配的关键部分。

您可以运行该程序以查看一些操作计数统计信息。例如,输出

Target 5=<de?ga>
012345678901234567890123456789012345678901
abc1efgabc2efgabcde3gabcdefg4bcdefgabc5efg

@  17, de?ga  and  de3ga  match
@  24, de?ga  and  defg4  differ
@  31, de?ga  and  defga  match
Advances:  'd' 0+3  'e' 3+3  'g' 3+3  = 6+9 = 15

表示步骤2G输入3次(即关键字符匹配3次);完全比较成功了两次;步骤2E while循环高级索引6次;步骤2I高级索引9次;总共有15个进步,用于搜索42个字符的字符串以获得目标。

/* jiw 
$Id: stringsearch.c,v 1.2 2011/08/19 08:53:44 j-waldby Exp j-waldby $

Re: Concept-code for searching a long string for short targets,
    where targets may contain wildcard characters.

The user can enter any number of targets as command line parameters.
This code has 2 long strings available for testing; if the first
character of the first parameter is '1' the jay[42] string is used,
else kay[321].

Eg, for tests with *hay = jay use command like

   ./stringsearch 1e?g a?cd bc?e?g c?efg de?ga ddee? ddee?f

or with *hay = kay,

   ./stringsearch  bc?e?  jih?  pa?j  ?av??j

to exercise program.

Copyright 2011 James Waldby.  Offered without warranty
under GPL v3 terms as at http://www.gnu.org/licenses/gpl.html
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <limits.h>
//================================================
int main(int argc, char *argv[]) {
  char jay[]="abc1efgabc2efgabcde3gabcdefg4bcdefgabc5efg";
  char kay[]="ludehkhtdiokihtmaihitoia1htkjkkchajajavpajkihtijkhijhipaja"
    "etpajamhkajajacpajihiatokajavtoia2pkjpajjhiifakacpajjhiatkpajfojii"
    "etkajamhpajajakpajihiatoiakavtoia3pakpajjhiifakacpajjhkatvpajfojii"
    "ihiifojjjjhijpjkhtfdoiajadijpkoia4jihtfjavpapakjhiifjpajihiifkjach"
    "ihikfkjjjjhijpjkhtfdoiajakijptoik4jihtfjakpapajjkiifjpajkhiifajkch";
  char *hay = (argc>1 && argv[1][0]=='1')? jay:kay;

  enum { chars=1<<CHAR_BIT, TsizMax=40, Lsiz=TsizMax+sizeof kay, L1, L2 };
  int L[L2], H[chars], T[chars], g, k, par;

  // Step 1.  Make arrays L, H, T.
  for (k=0; k<chars; ++k) H[k] = T[k] = L1; // Init H and T
  for (g=0; hay[g]; ++g) {  // Make linked character lists for hay.
    k = hay[g];            // In same loop, could count char freqs.
    if (T[k]==L1) H[k] = T[k] = g;
    T[k] = L[T[k]] = g;
  }

  // Step 2.  Read and process target strings.
  for (par=1; par<argc; ++par) {
    int alpha[3], at[3], a=g, b=g, c=g, da, dab, dbc, dac, i, j, r;
    char * targ = argv[par];
    enum { wild = '?' };
    int sa=0, sb=0, sc=0, ta=0, tb=0, tc=0;
    printf ("Target %d=<%s>\n", par, targ);

    // Step 2A.  Choose 3 non-wild characters to follow.
    // As is, chooses first 3 non-wilds for a,b,c.
    // Could instead choose 3 rarest characters.
    for (j=0; j<3; ++j) alpha[j] = -j;
    for (i=j=0; targ[i] && j<3; ++i)
      if (targ[i] != wild) {
        r = alpha[j] = targ[i];
        if (alpha[0]==alpha[1] || alpha[1]==alpha[2]
            || alpha[0]==alpha[2]) continue;
        at[j] = i;
        L[T[r]] = L[g+i] = g+i;
        ++j;
      }
    if (j != 3) {
      printf ("  Too few target chars\n"); 
      continue;
    }

    // Step 2B.  Set a,b,c to head-of-list locations, set deltas.
    da  = at[0];
    a = H[alpha[0]];  dab = at[1]-at[0];
    b = H[alpha[1]];  dbc = at[2]-at[1];
    c = H[alpha[2]];  dac = at[2]-at[0];

    // Step 2C.  See if key characters appear in haystack
    if (a >= g || b >= g || c >= g) {
      printf ("  No match on some character\n");
      continue;      
    }

    for (g=0; hay[g]; ++g) printf ("%d", g%10);
    printf ("\n%s\n", hay);    // Show haystack, for user aid

    // Step 2D.  Search for match
    while (c < g) {
      // Step 2E.  Enforce delta distances
      while (a+dab < b) {a = L[a]; ++sa; } // Replicate these
      while (b+dbc < c) {b = L[b]; ++sb; } //  3 abc lines as many 
      while (a+dac > c) {c = L[c]; ++sc; } //   times as you like.
      while (a+dab < b) {a = L[a]; ++sa; } // Replicate these
      while (b+dbc < c) {b = L[b]; ++sb; } //  3 abc lines as many 
      while (a+dac > c) {c = L[c]; ++sc; } //   times as you like.

      // Step 2F.  See if delta distances were met
      if (a+dab==b && b+dbc==c && c<g) {
        // Step 2G.  Yes, so we have 3-letter-match and need to test whole match.
        r = a-da;
        for (k=0; targ[k]; ++k)
          if ((hay[r+k] != targ[k]) && (targ[k] != wild))
            break;
        printf ("@ %3d, %s  and  ", r, targ);
        for (i=0; targ[i]; ++i) putchar(hay[r++]);
        // Step 2H.  Report match, if found
        puts (targ[k]? "  differ" : "  match");
        // Step 2I.  Advance all of a,b,c, to go on looking
        a = L[a]; ++ta;
        b = L[b]; ++tb;
        c = L[c]; ++tc;
      }
    }
    printf ("Advances:  '%c' %d+%d  '%c' %d+%d  '%c' %d+%d  = %d+%d = %d\n",
        alpha[0], sa,ta, alpha[1], sb,tb, alpha[2], sc,tc,
        sa+sb+sc, ta+tb+tc, sa+sb+sc+ta+tb+tc);
  }
  return 0;
}

请注意,如果您比当前首选答案更喜欢此答案,请取消标记该答案并标记此答案。 :)

答案 2 :(得分:0)

我认为正则表达式通常使用finite state automation-based search。尝试实现它。