以下问题在2008年被问及2003年的一些代码。正如OP的更新所显示的那样,整篇文章已经被2008年的老式算法淘汰,并且仅作为历史好奇而存在。
我需要在C / C ++中进行快速不区分大小写的子字符串搜索。我的要求如下:
这是我正在使用的当前实现(取自GNU C库):
/* Return the offset of one string within another.
Copyright (C) 1994,1996,1997,1998,1999,2000 Free Software Foundation, Inc.
This file is part of the GNU C Library.
The GNU C Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
The GNU C Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with the GNU C Library; if not, write to the Free
Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
02111-1307 USA. */
/*
* My personal strstr() implementation that beats most other algorithms.
* Until someone tells me otherwise, I assume that this is the
* fastest implementation of strstr() in C.
* I deliberately chose not to comment it. You should have at least
* as much fun trying to understand it, as I had to write it :-).
*
* Stephen R. van den Berg, berg@pool.informatik.rwth-aachen.de */
/*
* Modified to use table lookup instead of tolower(), since tolower() isn't
* worth s*** on Windows.
*
* -- Anders Sandvig (anders@wincue.org)
*/
#if HAVE_CONFIG_H
# include <config.h>
#endif
#include <ctype.h>
#include <string.h>
typedef unsigned chartype;
char char_table[256];
void init_stristr(void)
{
int i;
char string[2];
string[1] = '\0';
for (i = 0; i < 256; i++)
{
string[0] = i;
_strlwr(string);
char_table[i] = string[0];
}
}
#define my_tolower(a) ((chartype) char_table[a])
char *
my_stristr (phaystack, pneedle)
const char *phaystack;
const char *pneedle;
{
register const unsigned char *haystack, *needle;
register chartype b, c;
haystack = (const unsigned char *) phaystack;
needle = (const unsigned char *) pneedle;
b = my_tolower (*needle);
if (b != '\0')
{
haystack--; /* possible ANSI violation */
do
{
c = *++haystack;
if (c == '\0')
goto ret0;
}
while (my_tolower (c) != (int) b);
c = my_tolower (*++needle);
if (c == '\0')
goto foundneedle;
++needle;
goto jin;
for (;;)
{
register chartype a;
register const unsigned char *rhaystack, *rneedle;
do
{
a = *++haystack;
if (a == '\0')
goto ret0;
if (my_tolower (a) == (int) b)
break;
a = *++haystack;
if (a == '\0')
goto ret0;
shloop:
;
}
while (my_tolower (a) != (int) b);
jin:
a = *++haystack;
if (a == '\0')
goto ret0;
if (my_tolower (a) != (int) c)
goto shloop;
rhaystack = haystack-- + 1;
rneedle = needle;
a = my_tolower (*rneedle);
if (my_tolower (*rhaystack) == (int) a)
do
{
if (a == '\0')
goto foundneedle;
++rhaystack;
a = my_tolower (*++needle);
if (my_tolower (*rhaystack) != (int) a)
break;
if (a == '\0')
goto foundneedle;
++rhaystack;
a = my_tolower (*++needle);
}
while (my_tolower (*rhaystack) == (int) a);
needle = rneedle; /* took the register-poor approach */
if (a == '\0')
break;
}
}
foundneedle:
return (char*) haystack;
ret0:
return 0;
}
您能否更快地制作此代码,或者您知道更好的实施吗?
注意:我注意到GNU C库现在有a new implementation of strstr()
,但我不确定它是否容易被修改为不区分大小写,或者它实际上是比旧的更快(在我的情况下)。我也注意到the old implementation is still used for wide character strings,所以如果有人知道原因,请分享。
更新
只是为了清楚 - 如果它还没有 - 我没有编写这个函数,它是GNU C库的一部分。我只是将其修改为不区分大小写。
另外,感谢关于strcasestr()
的提示并检查其他来源的其他实现(如OpenBSD,FreeBSD等)。这似乎是要走的路。上面的代码来自2003年,这就是我在这里发布的原因,希望有更好的版本可用,显然它是。 :)
答案 0 :(得分:12)
您发布的代码大约是strcasestr
的一半。
$ gcc -Wall -o my_stristr my_stristr.c
steve@solaris:~/code/tmp
$ gcc -Wall -o strcasestr strcasestr.c
steve@solaris:~/code/tmp
$ ./bench ./my_stristr > my_stristr.result ; ./bench ./strcasestr > strcasestr.result;
steve@solaris:~/code/tmp
$ cat my_stristr.result
run 1... time = 6.32
run 2... time = 6.31
run 3... time = 6.31
run 4... time = 6.31
run 5... time = 6.32
run 6... time = 6.31
run 7... time = 6.31
run 8... time = 6.31
run 9... time = 6.31
run 10... time = 6.31
average user time over 10 runs = 6.3120
steve@solaris:~/code/tmp
$ cat strcasestr.result
run 1... time = 3.82
run 2... time = 3.82
run 3... time = 3.82
run 4... time = 3.82
run 5... time = 3.82
run 6... time = 3.82
run 7... time = 3.82
run 8... time = 3.82
run 9... time = 3.82
run 10... time = 3.82
average user time over 10 runs = 3.8200
steve@solaris:~/code/tmp
main
功能是:
int main(void)
{
char * needle="hello";
char haystack[1024];
int i;
for(i=0;i<sizeof(haystack)-strlen(needle)-1;++i)
{
haystack[i]='A'+i%57;
}
memcpy(haystack+i,needle, strlen(needle)+1);
/*printf("%s\n%d\n", haystack, haystack[strlen(haystack)]);*/
init_stristr();
for (i=0;i<1000000;++i)
{
/*my_stristr(haystack, needle);*/
strcasestr(haystack,needle);
}
return 0;
}
经过适当修改以测试两种实现方式。我注意到,当我打字时,我离开了init_stristr
电话,但它不应该改变太多。 bench
只是一个简单的shell脚本:
#!/bin/bash
function bc_calc()
{
echo $(echo "scale=4;$1" | bc)
}
time="/usr/bin/time -p"
prog="$1"
accum=0
runs=10
for a in $(jot $runs 1 $runs)
do
echo -n "run $a... "
t=$($time $prog 2>&1| grep user | awk '{print $2}')
echo "time = $t"
accum=$(bc_calc "$accum+$t")
done
echo -n "average user time over $runs runs = "
echo $(bc_calc "$accum/$runs")
答案 1 :(得分:7)
您可以使用StrStrI函数查找字符串中第一次出现的子字符串。比较不区分大小写。 不要忘记包括它的标题 - Shlwapi.h。 看看这个:http://msdn.microsoft.com/en-us/library/windows/desktop/bb773439(v=vs.85).aspx
答案 2 :(得分:3)
独立于平台使用:
from matplotlib.spines import Spine
from matplotlib.path import Path
ax.spines[u'middle'] = Spine( ax, 'right', Path(([[ 0., -1.], [ 0., 1.]]), None), linestyle='--', linewidth=1, facecolor=[0,0,0], zorder=1, transform = ax.transData )
答案 3 :(得分:2)
为什么使用_strlwr(string);在init_stristr()?它不是标准功能。大概是为了支持语言环境,但由于它不是标准的,我只是使用:
char_table[i] = tolower(i);
答案 4 :(得分:2)
使用boost string algo。它是可用的,跨平台的,只有一个头文件(没有要链接的库)。更不用说你应该使用boost了。
#include <boost/algorithm/string/find.hpp>
const char* istrstr( const char* haystack, const char* needle )
{
using namespace boost;
iterator_range<char*> result = ifind_first( haystack, needle );
if( result ) return result.begin();
return NULL;
}
答案 5 :(得分:1)
我建议你采取一些已经存在的常见strcasestr实现。例如glib,glibc,OpenBSD,FreeBSD等。您可以使用google.com/codesearch搜索更多内容。然后,您可以进行一些性能测量并比较不同的实现。
答案 6 :(得分:1)
假设两个输入字符串都是小写的。
int StringInStringFindFirst(const char* p_cText, const char* p_cSearchText)
{
int iTextSize = strlen(p_cText);
int iSearchTextSize = strlen(p_cSearchText);
char* p_cFound = NULL;
if(iTextSize >= iSearchTextSize)
{
int iCounter = 0;
while((iCounter + iSearchTextSize) <= iTextSize)
{
if(memcmp( (p_cText + iCounter), p_cSearchText, iSearchTextSize) == 0)
return iCounter;
iCounter ++;
}
}
return -1;
}
您也可以尝试使用蒙版...例如,如果您要比较的大多数字符串只包含从a到z的字符,那么可能值得这样做。
long GetStringMask(const char* p_cText)
{
long lMask=0;
while(*p_cText != '\0')
{
if (*p_cText>='a' && *p_cText<='z')
lMask = lMask | (1 << (*p_cText - 'a') );
else if(*p_cText != ' ')
{
lMask = 0;
break;
}
p_cText ++;
}
return lMask;
}
则...
int main(int argc, char* argv[])
{
char* p_cText = "this is a test";
char* p_cSearchText = "test";
long lTextMask = GetStringMask(p_cText);
long lSearchMask = GetStringMask(p_cSearchText);
int iFoundAt = -1;
// If Both masks are Valid
if(lTextMask != 0 && lSearchMask != 0)
{
if((lTextMask & lSearchMask) == lSearchMask)
{
iFoundAt = StringInStringFindFirst(p_cText, p_cSearchText);
}
}
else
{
iFoundAt = StringInStringFindFirst(p_cText, p_cSearchText);
}
return 0;
}
答案 7 :(得分:1)
这不会考虑区域设置,但如果您可以更改IS_ALPHA和TO_UPPER,则可以考虑它。
#define IS_ALPHA(c) (((c) >= 'A' && (c) <= 'Z') || ((c) >= 'a' && (c) <= 'z'))
#define TO_UPPER(c) ((c) & 0xDF)
char * __cdecl strstri (const char * str1, const char * str2){
char *cp = (char *) str1;
char *s1, *s2;
if ( !*str2 )
return((char *)str1);
while (*cp){
s1 = cp;
s2 = (char *) str2;
while ( *s1 && *s2 && (IS_ALPHA(*s1) && IS_ALPHA(*s2))?!(TO_UPPER(*s1) - TO_UPPER(*s2)):!(*s1-*s2))
++s1, ++s2;
if (!*s2)
return(cp);
++cp;
}
return(NULL);
}
答案 8 :(得分:0)
如果你想摆脱CPU周期,你可能会考虑这个 - 让我们假设我们处理的是ASCII而不是Unicode。
创建一个包含256个条目的静态表。表中的每个条目都是256位。
要测试两个字符是否相等,您可以执行以下操作:
if (BitLookup(table[char1], char2)) { /* match */ }
要构建表,在表[char1]中设置一个位,您认为它与char2匹配。因此,在构建表时,您可以在“a”条目(以及“A”条目)中设置“a”和“A”索引处的位。
现在进行位查找会很慢(位查找将是移位,掩码和最有可能添加),因此您可以使用字节表,因此您使用8位表示1位。这将需要32K - 如此万岁 - 你已经达成了时间/空间权衡!我们可能希望使表更灵活,所以我们假设我们这样做 - 表格将定义同余。
当且仅当有一个函数将它们定义为等效时,才认为两个字符是全等的。所以'A'和'a'对于不区分大小写是一致的。 'A','À','Á'和''与变音不敏感是一致的。
因此,您定义与您的一致性相对应的位域
#define kCongruentCase (1 << 0)
#define kCongruentDiacritical (1 << 1)
#define kCongruentVowel (1 << 2)
#define kCongruentConsonant (1 << 3)
然后你的测试是这样的:
inline bool CharsAreCongruent(char c1, char c2, unsigned char congruency)
{
return (_congruencyTable[c1][c2] & congruency) != 0;
}
#define CaseInsensitiveCharEqual(c1, c2) CharsAreCongruent(c1, c2, kCongruentCase)
这种摆弄巨大表格的东西是ctype的核心,由by。
答案 9 :(得分:0)
如果您可以控制针头串,使其始终为小写,那么您可以编写stristr()的修改版本以避免对其进行查找,从而加快代码速度。它不是一般的,但它可以更快 - 稍快一点。类似的评论适用于大海捞针,但您更有可能从您无法控制的来源读取干草堆,因为您无法确定数据是否符合要求。
性能的提升是否值得,这是另一个问题。对于99%的应用程序,答案是“不,它不值得”。您的申请可能是重要的少数几个。更可能的是,它不是。