2009-08-09 5 views
3

필자는 0과 1의 선형 목록을 가지고 있으며, 여러 개의 간단한 패턴을 일치시키고 첫 번째 발생을 찾아야합니다. 예를 들어, 0001101101, 01010100100 또는 10100100010을 길이 8 백만 개 목록에서 찾아야 할 수 있습니다. 그 중 하나의 첫 번째 발생을 찾아야 만 발생하는 인덱스를 반환하면됩니다. 그러나 루핑을 수행하고 큰 목록을 통해 액세스하는 것은 비용이 많이들 수 있으며 너무 많이하지 않을 것입니다.가 BTW, 나는 위의 의사에 따라이 프로그램을 구현, 성능은 OK,하지만 아무것도 장관 :선형 패턴 일치를위한 알고리즘?

foreach (patterns) { 
    for (i=0; i < listLength; i++) 
     for(t=0; t < patternlength; t++) 
      if(list[i+t] != pattern[t]) { 
       break; 
      } 
      if(t == patternlength - 1) { 
       return i; // pattern found! 
      } 
     } 
    } 
} 

편집을하는 것보다 더 빠른 방법이있다. 나는 내 프로세서의 단일 코어에서 약 600 만 비트를 초당 처리하는 것으로 추산하고 있습니다. 저는 이것을 이미지 프로세싱에 사용하고 있으며, 수천 개의 8 메가 픽셀 이미지를 거쳐야하기 때문에 조금씩 도움이 될 것입니다.

편집 : 명확하지 않은 경우 비트 배열로 작업하므로 ONE 및 ZERO의 두 가지 가능성이 있습니다. 그리고 그것은 C++입니다.

편집 : BM 및 KMP 알고리즘에 대한 포인터를 보내 주셔서 감사합니다. 나는 몇 가지 알고리즘과는 달리 (BM에 대한 위키 백과 페이지에서이

이 알고리즘은 대한 를 검색하고있는 대상 문자열 (키)를 전처리 말한다,하지만 문자열에 를 검색하고, 주목 그 검색 할 문자열을 사전 처리합니다 다음 을 반복적으로 검색하여 의 전처리 비용을 상각합니다.

재미있어 보이지만 그런 알고리즘의 예는 제공하지 않았습니다. 그런 것도 도움이 될까요?

+0

어떤 언어를 사용하고 계시거나 사용할 수 있습니까? 보다 확실한 알고리즘이 이미 구현되었습니다. (보너스 : 어떤 하드웨어를 사용하고 있습니까? 클라우드를 빌려보고 맵핑/문제를 줄입니다!) – pilcrow

+0

C++. 지금 당장 내 프로그램은 몇 백 라인의 코드와 하나의 클래스로 구성되어 있습니다 ... 나는 더 단순한 것으로 유지하고 더 이상의 제 3 자 코드를 통합하지 않을 것입니다. – erjiang

답변

11

인터넷 검색을위한 키 "멀티 패턴"문자열 일치한다.

위로 가기 1975 년 Aho and Corasickfgrep의 원본 버전에서 사용 된 (선형 시간) 알고리즘을 게시했습니다. 그 알고리즘은 많은 연구자들에 의해 정제되었습니다.예를 들어, Commentz-Walter (1979) 결합 된 Aho & Corasick Boyer&Moore과 일치합니다. AC를 Boyer-Moore-Horspool 변형과 결합한 Baeza-Yates (1989)Wu and Manber (1994)도 비슷한 작업을 수행했습니다.

다중 패턴 매칭 알고리즘의 AC 라인의 대안은 Rabin and Karp's 알고리즘입니다.

저는 Aho-Corasick과 Rabin-Karp Wikipedia 페이지를 읽고 나서 시작하는 것이 좋습니다. 그렇다면 이미 사용 가능한 언어/런타임에 대한 구현이 이미있을 수 있습니다.

+1

필자는 개인적으로 rabin-karp를 사용하는 것이 좋습니다. 다중 패턴 검색은 물론 상대적으로 구현하기 쉬운 흙 이기에 좋습니다. 원격으로 어려운 부분은 여기에서 읽을 수있는 롤링 해시입니다 (http://en.wikipedia.org/wiki/Rolling_hash) – Falaina

2

당신은 SuffixArray를 빌드하고 실행 미친 검색 할 수 있습니다 : (길이 (패턴)) O. 하지만 그 배열을 만들어야합니다. 텍스트가 정적이고 패턴이 동적 일 때만 가치가 있습니다.

0

문자열이 유연해야하는 경우 Mitch Wheat이 수정 한 "Boyer-Moore 문자열 검색 알고리즘"을 권장합니다. 문자열이 유연해야 할 필요가없는 경우 패턴 매칭을 더 많이 축소 할 수 있어야합니다. Boyer-Moore의 모델은 다수의 문자열 중 하나가 일치하는 많은 양의 데이터를 검색하는 데 매우 효율적입니다.

야곱

+0

"유연한"이란 무엇을 의미합니까? – erjiang

+0

죄송합니다. 피곤했습니다. 본질적으로 Turbo Boyer-Moore 알고리즘 (http://www-igm.univ-mlv.fr/~lecroq/string/node15.html)을 생각했지만 그 이름을 기억할 수 없었습니다. BM의 좋은 점은 검색중인 모든 문자열을 사전 처리하고 주 문자열을 한 번 실행하여 모든 문자열을 검색하는 것입니다. 이것이 주 문자열을 사전 처리하지 않는 이유입니다. 그것은 단지 하나의 통과를 만든다. 물론 기본 BM은 3n 개의 비교를 가질 수 있습니다 (n은 검색 할 문자열의 크기 임). 터보 BM은 2n으로 제한됩니다. – TheJacobTaylor

0

0과 1이 교대로 반복되는 경우 텍스트를 실행으로 인코딩하십시오. n 0의 실행은 -n이고 n 1의 실행은 n입니다. 그런 다음 검색 문자열을 인코딩하십시오. 그런 다음 인코딩 된 문자열을 사용하는 검색 함수를 만듭니다.

코드는 다음과 같습니다 : 문자열에

try: 
    import psyco 
    psyco.full() 
except ImportError: 
    pass 

def encode(s): 
    def calc_count(count, c): 
     return count * (-1 if c == '0' else 1) 
    result = [] 
    c = s[0] 
    count = 1 
    for i in range(1, len(s)): 
     d = s[i] 
     if d == c: 
      count += 1 
     else: 
      result.append(calc_count(count, c)) 
      count = 1 
      c = d 
    result.append(calc_count(count, c)) 
    return result 

def search(encoded_source, targets): 
    def match(encoded_source, t, max_search_len, len_source): 
     x = len(t)-1 
     # Get the indexes of the longest segments and search them first 
     most_restrictive = [bb[0] for bb in sorted(((i, abs(t[i])) for i in range(1,x)), key=lambda x: x[1], reverse=True)] 
     # Align the signs of the source and target 
     index = (0 if encoded_source[0] * t[0] > 0 else 1) 
     unencoded_pos = sum(abs(c) for c in encoded_source[:index]) 
     start_t, end_t = abs(t[0]), abs(t[x]) 
     for i in range(index, len(encoded_source)-x, 2): 
      if all(t[j] == encoded_source[j+i] for j in most_restrictive): 
       encoded_start, encoded_end = abs(encoded_source[i]), abs(encoded_source[i+x]) 
       if start_t <= encoded_start and end_t <= encoded_end: 
        return unencoded_pos + (abs(encoded_source[i]) - start_t) 
      unencoded_pos += abs(encoded_source[i]) + abs(encoded_source[i+1]) 
      if unencoded_pos > max_search_len: 
       return len_source 
     return len_source 
    len_source = sum(abs(c) for c in encoded_source) 
    i, found, target_index = len_source, None, -1 
    for j, t in enumerate(targets): 
     x = match(encoded_source, t, i, len_source) 
     print "Match at: ", x 
     if x < i: 
      i, found, target_index = x, t, j 
    return (i, found, target_index) 


if __name__ == "__main__": 
    import datetime 
    def make_source_text(len): 
     from random import randint 
     item_len = 8 
     item_count = 2**item_len 
     table = ["".join("1" if (j & (1 << i)) else "0" for i in reversed(range(item_len))) for j in range(item_count)] 
     return "".join(table[randint(0,item_count-1)] for _ in range(len//item_len)) 
    targets = ['0001101101'*2, '01010100100'*2, '10100100010'*2] 
    encoded_targets = [encode(t) for t in targets] 
    data_len = 10*1000*1000 
    s = datetime.datetime.now() 
    source_text = make_source_text(data_len) 
    e = datetime.datetime.now() 
    print "Make source text(length %d): " % data_len, (e - s) 
    s = datetime.datetime.now() 
    encoded_source = encode(source_text) 
    e = datetime.datetime.now() 
    print "Encode source text: ", (e - s) 

    s = datetime.datetime.now() 
    (i, found, target_index) = search(encoded_source, encoded_targets) 
    print (i, found, target_index) 
    print "Target was: ", targets[target_index] 
    print "Source matched here: ", source_text[i:i+len(targets[target_index])] 
    e = datetime.datetime.now() 
    print "Search time: ", (e - s) 

두 배 당신이 제공 한, 1 천만 자에 세 대상의 최초의 일치를 찾을 일곱 초 정도 걸립니다. 물론, 임의의 텍스트를 사용하고 있기 때문에, 각 실행마다 조금씩 다릅니다.

psyco는 런타임에 코드를 최적화하기위한 파이썬 모듈입니다. 이를 사용하면 뛰어난 성능을 얻을 수 있으며 C/C++ 성능의 상한선으로 추정 할 수 있습니다. 여기에 최근 실적은 다음과 같습니다

Make source text(length 10000000): 0:00:02.277000 
Encode source text: 0:00:00.329000 
Match at: 2517905 
Match at: 494990 
Match at: 450986 
(450986, [1, -1, 1, -2, 1, -3, 1, -1, 1, -1, 1, -2, 1, -3, 1, -1], 2) 
Target was: 1010010001010100100010 
Source matched here: 1010010001010100100010 
Search time: 0:00:04.325000 

그것은에 대한 세 가지 인코딩 된 문자열을 검색하기 위해 1000 만 개 문자와 약 4 초를 인코딩하는 약 300 밀리 초 걸립니다. 나는 인코딩 시간이 C/C++에서 높을 것이라고 생각하지 않는다.

+0

8 백만 비트를 실행으로 인코딩하는 데 걸리는 시간이 걱정됩니다. 몇 가지 검색을 수행 한 다음 원시 비트로 다시 디코딩하면 원시 비트에서 BM 또는 KMP 검색을 수행하는 것보다 많은 시간이 걸릴 수 있습니다. – erjiang

+0

확실히 가능합니다. – hughdbrown

1

효율적으로 될 수있는 솔루션 :

  1. 저장 다음 pattern_length 문자가 트라이에있는 경우
  2. 시작 목록을
  3. 체크를 한 검색 trie 데이터 구조의 패턴에 중지 성공 (O (1) 작업)
  4. 1 단계 char 및 반복 # 3

목록을 변경할 수없는 경우 다음 번 계산 반복을 피하기 위해 일치하는 패턴의 오프셋을 저장할 수 있습니다.

0

비트 배열이라면 롤링 합계를 향상시키는 것이 좋습니다. 패턴 길이가 n 인 경우 첫 번째 n 비트를 합하고 패턴 합계와 일치하는지 확인합니다. 항상 첫 번째 비트를 저장하십시오. 그런 다음, 모든 다음 비트마다 합에서 첫 번째 비트를 빼고 다음 비트를 추가하고 합계가 패턴의 합과 일치하는지 확인하십시오. 그러면 패턴 위에 선형 루프가 저장됩니다.

BM 알고리즘이 이처럼 멋진 것처럼 보이지 않습니다. 여기서는 두 가지 가능한 값, 0과 1 만 있기 때문에 첫 번째 테이블은 전체적으로 도움이되지 않습니다. 두 번째 테이블이 도움이 될지도 모르겠다. 그러나 그것은 BMH가 거의 쓸모 없다는 것을 의미한다.

편집 : 내 수면 박탈 상태에서 내가 BM을 이해할 수 없었다, 그래서 난 그냥이 롤링 합계를 (정말 쉬웠다) 구현이 3 배 빠르게 내 검색을했다. "롤링 해시"를 언급 한 사람 덕분입니다.17.3 초 전에 비해 321,750,000 비트를 검색하여 두 개의 30 비트 패턴을 5.45 초 (단일 스레드)로 검색 할 수 있습니다.