가장 빠른 방법은 대소문자를 구분하는 문자열 검색,C/C++?
-
03-07-2019 - |
문제
참고
아래의 질문을 요청했다고 2008 년에 일부에 대해 코드에서 2003.으로는 영업 이익의 업데이트 이 전체 게시물을 사용해서 빈티지 2008 알고리즘을 지속하기로는 역사적 호기심.
나는 할 필요가 빠른 대소문자를 구분 부분에서 검색 C/C++.나의 요구 사항은 다음과 같습니다:
- 처럼 행동해야한 strstr()(i.e포인터를 반환하는 경기점).
- 대/소문자를 구분하지 않아야 합(doh).
- 해 지원됩니다.
- 에서 사용할 수 있어야 합니다(Windows MSVC++8.0)나 쉽게 휴대용하여 윈도우(i.e에서는 오픈 소스 라이브러리).
여기에는 현재 구현이 나를 사용하여(에서 촬영 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 라이브러리는 지금 새로운 구현 strstr()
, 지만,나는 확실하지 않는 방법은 쉽게 수정할 수 있습니다.을 대/소문자를 구분하지 않는 경우,또는 그 사실보다 더 빨리 오래된 중 하나는(내 경우).이 이전 구현은 여전히 사용되는 와이드 문자열, 다,그래서 누구나 알고있는 경우 왜,공유하시기 바랍니다.
업데이트
그냥 가지 분명한 경우에 그것은지 이미 나도 쓰지 않았어요 이 기능은,그것의 일부 의 GNU C 라이브러리입니다.나는 단지 그것을 수정할 경우를 구분하지 않습니다.
또한,에 대한 감사에 대한 팁 strcasestr()
고 확인하는 다른 구현는 다른 소스에서(처럼 오픈 bsd,FreeBSD,etc.).그것은 있을 것 같은 방법입니다.위 코드에서는,2003 년은 왜 게시 그것은 여기에서 희망에 대한 더 나은 버전을 사용할 수 있는 분명히 그것입니다.:)
해결책
이 코드는 게시된 약 절반만큼 빨리 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
단순한 쉘 스크립트:
#!/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")
다른 팁
당신이 사용할 수 있습 StrStrI 있는 기능을 발견한 첫 번째 항목의 하위 문자열에는 문자열을 사용합니다.비교는 대소문자를 구분하지 않습니다.을 포함하는 것을 잊지 마세요 헤더-Shlwapi.h.이 체크아웃: http://msdn.microsoft.com/en-us/library/windows/desktop/bb773439(v=vs.85).aspx
플랫폼 독립적인 사용:
const wchar_t *szk_wcsstri(const wchar_t *s1, const wchar_t *s2)
{
if (s1 == NULL || s2 == NULL) return NULL;
const wchar_t *cpws1 = s1, *cpws1_, *cpws2;
char ch1, ch2;
bool bSame;
while (*cpws1 != L'\0')
{
bSame = true;
if (*cpws1 != *s2)
{
ch1 = towlower(*cpws1);
ch2 = towlower(*s2);
if (ch1 == ch2)
bSame = true;
}
if (true == bSame)
{
cpws1_ = cpws1;
cpws2 = s2;
while (*cpws1_ != L'\0')
{
ch1 = towlower(*cpws1_);
ch2 = towlower(*cpws2);
if (ch1 != ch2)
break;
cpws2++;
if (*cpws2 == L'\0')
return cpws1_-(cpws2 - s2 - 0x01);
cpws1_++;
}
}
cpws1++;
}
return NULL;
}
왜 당신이 사용하 _strlwr(string);에 init_stristr()?그것의 표준 함수입니다.아마도 그것은 로케일이 지원하지만,그것은 표준,나는 사용:
char_table[i] = tolower(i);
사 부스트 문자열 algo.사용 가능한 크로스 플랫폼 및 헤더 파일이(아 라이브러리 링크에서).당신은 말할 것도 없이 사용해야 향상을 어쨌든.
#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;
}
나는 조언이 당신의 일부를 가지 일반적인 strcasestr 구현에 이미 존재합니다.예를 들어의 입심,glibc,오픈 bsd,FreeBSD,etc.검색할 수 있습니다 더 google.com/codesearch.당신은 다음을 할 수 있습니다 몇 가지 성과 측정을 비교하며 다른 구현합니다.
가정 모두 입력 문자열을 이미 소문자입니다.
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;
}
이 고려하지 않을 것이다 locale 지만,이 경우 변경할 수 있습니다 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);
}
하려는 경우 흘리는 CPU 사이클을 고려할 수 있습니다 이다고 가정하자는 우리가 해결해야 할 문제 ASCII 과하지 않는 유니코드를 기반으로 합니다.
을 정적 테이블을 가진 256 항목이 있습니다.테이블의 각 항목에는 256 비트입니다.
는지 여부를 테스트하거나지 않은 두 개의 문자가 동일한,당신은 다음과 같은 것이 가능합니다.
if (BitLookup(table[char1], char2)) { /* match */ }
를 구축하는 테이블,당신은 조금 어디에서나 테이블[char1]어디에 당신이 그것을 고려의 일치에 대한 char2.그래서 건물에 테이블을 설정 비트는 인덱스에서는'a','A'와'a 번째 항목(고'로 번째 항목).
지금 이 될 것입니다 slowish 을 비 조회(금 보 변화 될 것입,마스크와 추가 가능성이 가장 높),그래서 당신이 사용할 수 있는 대신 테이블의 바이트 그래서 당신이 사용하는 8 비트를 나타내는 1 비트입니다.이 32K-도록 만세-당신은 시간/공간 무역에 그림을 즐길 수 있습니다.우리는 우리를 확인 할 수 있습니다 테이블에 더 유연하게,그래서 우리가 이렇게 대신-테이블이 정의 congruences 의 대신 합니다.
두 개의 문자로 간주됩 일치하는 경우에만 수있는 기능을 정의하는 그들로 동일합니다.그래서'A'와'은 적합한 대/소문자를 구분하지 않음.'A','À','A'와'Â'은 적합한 기 무신경.
그래서 정의하는 비트 필드에 해당하는 congruencies
#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)
이 비트의 종류와 조롱 ginormous 테이블의 핵심 ctype,에 의해습니다.
면을 제어할 수 있습니다 바늘 문자열도록 항상에서 더 낮은 경우,다음 작성할 수 있습니다 수정 버전 stristr()을 피하는 조회는,따라서 최대 속도는 코드입니다.그렇지 않으로 일반적이지만,그것은 빠를 수 있습니다-약간 더 빠르다.비슷한 의견을 적용되는 건초 더미,하지만 당신은 더 읽는 건초 더미의 소스에서 당신의 통제할 수 없는 데이터 요구에 응합니다.
지 성능 향상 가치가 그것을 모두 다른 질문.99%응용 프로그램의 대답은"아니,그럴 가치가 없습니다".응용 프로그램 중 하나가 될 수 있는 작은 소수가 그것이 문제입니다.더 많은 가능성이,그렇지 않습니다.