2009-09-15 5 views
0

제목은 다소 어색합니다. 나는 이것을 합산하는 방법을 정말로 모르고 있었다. 나는 내가 어떻게 이것을 할 수 있는지 안다, 나는 그것을 효율적으로하는 방법을 모른다. 여기에 내 문제가 :문자열 집합에서 문자열 순열을 검색합니다.

나는 입력으로 문자열이 있습니다.

foo는 바

그리고 (수만) 문자열의 매우 큰 집합이 :의가 있다고 가정 해 봅시다. 의가 있다고 가정 해 봅시다 :

foo는, 바즈, 바, 어쩌구, foo는 바, foo는 바즈

나는 세트에서 문자열 입력과 일치해야합니다. 이 경우 "foo", "bar"및 "foo bar"는 일치로 간주됩니다.

그래서, 어떻게 든 입력의 모든 순열을 검색해야합니다 (2 단어보다 길 수도 있습니다). 또는 사용자가 따옴표로 묶는 것을 의미한다면 어떻게 든 감지 할 수 있습니다. 또는 내가 생각하지 못한 무언가를 할 수도 있습니다.

여기에 사용할 수있는 데이터 구조 나 알고리즘이 있습니까? 어떻게해야합니까? 아니면이 유스 케이스를 처리하지 않아야합니까?

수정 : 문제를 왜곡 한 위의 오타가 있습니다. 위의 예에서 "foo baz"도 일치합니다. 미안합니다. 필자는 본질적으로 입력 단어의 순열을 사전에 매치하고 싶다. 따라서 "abc xyz"의 입력은 "123 abc"또는 "abc xyz"또는 "xyz 123"과 일치하지만 "abcxyz"는 일치하지 않습니다. 당신이 필요로하는 무엇

+0

이 문자열 영어 단어 또는 그들이 어떤 문자를 포함 할 수 있습니다? 대소 문자를 구분합니까? – Adamski

+0

@Adamski 영어 단어이므로 대소 문자를 구분하지 않습니다. 그러나 그들은 사전에서 찾을 수없는 것들과 같은 매우 기술적 인 단어입니다. –

+0

사전에 "foob"이 포함되어있는 경우 "foo"를 검색하거나 정확한 일치에만 관심이 있다면 반환하겠습니까? – Adamski

답변

2

입니다. 문자열을 키로 사용하고 문자열 목록을 값으로 사용하십시오. 검색 할 문자열을 토큰 화하고 각 문자열에 대해 전체 문자열을 사전에 추가하십시오. (Youn은 split 메소드를 사용하여 문자열을 토큰화할 수 있으며, 공백을 구분 기호로 사용하십시오.) 이후 조회가 필요할 때마다 검색 문자열을 토큰 화하고 사전의 각 토큰을 조회합니다. foo는, 바즈, 바, 어쩌구, foo는 바, foo는 바즈

귀하의 사전 항목이 있습니다 :

foo는 : foo는, 푸 바, foo는 바즈 바즈 다음과 같은 문자열을 추가 한 따라서 경우

: 바즈, foo는 바즈 바 : 바, foo는 바 ㅋ ㅋ : ㅋ ㅋ

당신은 다음

당신의 출력이 항목 foo는 아래에 저장하고 같은 바의 결합이다 "foo는 바"를 검색해야 그래서 : 는 "foo는 바는"foo는, 바에게 =

foo는 : foo는, 푸 바, foo는 바즈 조합 바 : 바, foo는 바

주는 : foo는, 푸 바, foo는 바즈, 바

편집 : 나는 단지 당신이 전체 또는 부분 일치만을 원한다는 것을 알아 차렸다. 즉, foo baz는 받아 들일 수 없다.쉬운 솔루션은 결과를 게시 처리하는 것입니다. 검색 문자열과 대상 문자열을 더 긴 길이로 제한하고 잘린 문자열을 수정되지 않은 문자열과 비교하십시오. 동등한 것만 수락하십시오.

EDIT : 그래서 foo baz가 실제로 일치합니다. 위 단락을 무시하십시오 (첫 번째 편집). 참조 (C#을) 코드는 다음과 같이

class DictionarySearch 
{ 
    private Dictionary<string, List<string>> dict; 

    public DictionarySearch() 
    { 
     dict = new Dictionary<string, List<string>>(); 
    } 

    /// <summary> 
    /// Add a string e.g. foo bar to the dictionary 
    /// </summary> 
    /// <param name="s">string to be added</param> 
    public void addString(string s) 
    { 
     //tokenize string 
     string[] words = s.Split(new char[] { ' ' }); 

     //add each token to the dictionary as a key with the matching value being s 
     foreach (string w in words) 
     { 
      if (dict.ContainsKey(w)) 
      { 
       dict[w].Add(s); 
      } 
      else 
      { 
       dict.Add(w, new List<string>()); 
       dict[w].Add(s); 
      } 
     } 
    } 
    /// <summary> 
    /// Find all strings which match at least one token 
    /// </summary> 
    /// <param name="s">string of tokens (words) to be matched</param> 
    /// <returns>List of strings matching at least one word</returns> 
    public IList<string> getMatches(string s) 
    { 
     //split search string into words 
     string[] words = s.Split(new char[] { ' ' }); 
     List<string> output = new List<string>(); 

     //retrieve from dictionary list of strings matching each word. 
     foreach (string w in words) 
     { 
      if (dict.ContainsKey(w)) 
      { 
       output.AddRange(dict[w]); 
      } 
      else 
      { 
       continue; 
      } 
     } 

     return output; 
    } 
} 

시간 복잡성이다 Q 문자열 당 단어와 n 개의 고유 한 말과 리터의 단어와 함께 검색 문자열과 m의 문자열 사전을 감안할 때 다음과 같이

데이터 구조 채우기 : O (q m T [사전 삽입]). 각 단어에 대해 삽입을 수행해야합니다.

문자열 찾기 : O (l * T [사전 찾기]). 검색 문자열에서 단어 당 사전 조회.

실제 비용은 사전 구현에 따라 다릅니다. 해시 테이블 기반 사전은 삽입 및 검색 모두에 대해 O (1) 비용을 발생시킵니다. 이진 트리 기반 사전은 삽입 및 검색 모두에 대해 O (lg n) 비용을 발생시킵니다.

1

내가 사전을 사용하는 것이 좋습니다 것 Lucene

+0

@Dennis : www.google.com/?q=Lucene 페이지가 존재하지 않습니다. 아마도 당신은 다음을 의미했습니다 : http://lucene.apache.org/java/docs/ – CPerkins

0

이 코드는 작동합니다. 그런 당신을 위해 충분히 효율적 알고하지 마십시오.

String[] dict = "foo bar".split(" "); 

    String[] array = new String[] { "foo", "baz", "bar", "blah", "foo bar", 
      "foo baz" }; 

    loop: for (String s : array) { 
     String[] a = s.split(" "); 

     for (String sample : dict) 
      for (String s1 : a) 
       if (sample.equals(s1)) { 
        System.out.println(s); 
        continue loop; 
       } 
    } 
1

을 (당신이, 당신은 아마 공간과 시간의 측면에서 더 명시해야 "효율적"말할 때하는 당신은 시간의 효율성을 의미하는 가정 수 있습니다 (주어진 당신 permutations 언급)).

String[] findStringsContaining(List<String> strings, String[] words) 

에 대한 응답을 계산하는 태스크가 중간 단계에서 무료 순전히 기능적 부작용이며, 그 결과가 결합 점을 감안 분배 및 실행 병렬 스레드로 핸드 오프 될 수있다 마지막 단계로. 나는. 단어 및/또는 문자열 목록을 분할 할 수 있습니다.

map-reduce 작품 (. 귀하의 경우, 자사의 모든 동일한 시스템에서 발생하는 것으로 무관) (단어의 각 스레드에 할당)

귀하의 매퍼는 방법입니다

boolean [] stringContainsWord (List<String> strings, String word); 

그 방법은 병렬로 실행됩니다.

부울 배열은 주어진 단어와 일치하는 각 색인 (목록)에 대해 참을 갖습니다.

와 감속기

(모든 매퍼가 완료된 후 실행)입니다 :
List<String> getMatchingList(List<String>, List<boolean[]> mapperResults); 

이 스레드에 대한 오버 헤드를 따로 두는 입력 단어의 합리적인 번호 매퍼 스레드 카운트 무시할 비용을 가정이 줄 것이다 O (n) (mapper) + O (m) (감속기의 경우) 시간 프로세스. 여기서 n은 문자열 목록에있는 항목의 수이고, m은 입력 된 단어의 수입니다.

각 단어에 대해 문자열 목록을 분할하고 실행중인 스레드를 분할하고 각 스레드가 문자열 목록의 하위 집합을 검색함으로써 매퍼에 대한 입력 목록이 다음과 같이되도록 작업을 병렬화 할 수 있습니다 전체 목록의 1/p 요소

-

당신이 문자열 목록이 거대하고, 특별히 경우 고려할 수있는 또 다른 방법, 내용이 langauge (예 : 영어 등)이며, 사실을 주어 최적화하는 대부분의 언어 그 언어로 된 문장의 대부분을 구성하는 단어의 집합이 상당히 적습니다. 예를 들어, 목록에 2 백만 개의 영문 문장이있는 경우 고유 단어 목록이 크기가 더 작아집니다 (예 : 몇 백 개).

이 경우 단어 -> 문장의지도를 가질 수 있으며 주어진 단어의 일치하는 문장에 대한 테스트가지도의 조회로 축소됩니다.

가 (여전히이 함께 초기 접근 방식을 결합 할 수 있습니다.) ejspencer의 아이디어에서

0

내가 함께

을이를 넣어
// Build the dictionary/data structure 
// O([average split length]*n) 
public static Dictionary<String,List<int>> BuildDictionary(String[] data) 
{ 
    String[] temp; 
    Dictionary<String,List<int>> dict = new Dictionary<String,List<int>>(); 
    for(int i = 0; i < data.length; i++) 
    { 
     temp = data[i].split(" "); 
     for(int j = 0; j < temp.length; j ++) 
     { 
      if(dict.get(temp[j]) == null) 
       dict.put(temp[j],new List<int>()); 

      dict.get(temp[j]).add(i); 
     } 
    } 

    return dict; 
} 

// find all the matches 
// O([average number of matches per key]*[input split length]) 
public static List<int> FindMatches(String input, Dictionary<String,List<int> dict) 
{ 
    String[] temp = input.split(" "); 
    List<int> ret = new List<int>(); 

    for(int i = 0; i < temp.length; i++) 
    { 
     if(dict.get(temp[i]) == null) 
      continue; // no match 

     // read the match into the return list, ignore copies 
     List<int> match = dict.get(temp[i]); 
     for(int j = 0; j < match.count(); j++) 
      if(!ret.contains(match.get(i)) 
       ret.add(match.get(i)); 
    } 

    return ret; 
} 

아마 스르르 컴파일되지 않습니다,하지만 난 당신을내는 ' 어쨌든 그것을 가지고 futz해야 할거야 그리고 이것은 당신에게 빠른 액세스와 간단한 코드 (공격적인 alphazero)에 대한 꽤 좋은 아이디어를 제공합니다.

이 검색은 대소 문자를 구별합니다. 그러나 사용자는이 검색을 사용하여 변경할 수 있습니다.

2

사전의 크기는 어느 정도입니까? 사전을 trie로 변환 할 수 있습니다. 사전을 trie로 변환하는 방법에 대한 사람들의 게시물이 있습니다. 일단 그렇게하면 조회가 간단하고 빠릅니다.

또한 간단한 해결책은 검색 문자열을 별도의 단어로 분리하여 트라이에서 각 단어를 검색하여 중복이 두 번 고려되지 않도록하는 것입니다.

1

큰 입력 문자열과 여러 단어로 된 사전의 경우 Rabin-Karp 또는 Aho-Corasick 알 고리 중 하나를 고려하십시오.

(라빈 - 카프 링크 - 내가 위의 참조 하이퍼 링크를 얻을 수없는 몇 가지 이유 - http://en.wikipedia.org/wiki/Rabin -Karp_string_search_algorithm)