2016-09-16 1 views
3

나는 90,000 개가 넘는 이름이있는 목록이 제공되었습니다. 나는 유사성이 50 % 이상인 이름을 검사하고 그 결과를 다음 형식으로 파일에 씁니다 :빠른 알고리즘을 사용하여 유사성 문자열 비교

ID 1, ID 2, 유사성 비율.

이미 유사성을 확인하는 알고리즘이 있지만 전체 목록을 반복하는 데 많은 시간이 걸립니다. 다른 사람이 이름을 비교하는 빠른 알고리즘을 도와 줄 수 있습니까?

아래 코드

public static void main(String[] args) throws IOException { 


    List<String> list = new ArrayList<>(); 
    int count = 0; 
    FileWriter f = new FileWriter(new File("output.txt")); 
    StringBuilder str = new StringBuilder(); 
    Scanner scanner = new Scanner(new File("name.csv")); 

    while (scanner.hasNextLine()) { 


     count++; 
     list.add(scanner.nextLine()); 

    } 


    long start = System.currentTimeMillis(); 

    ////////////////////////////////////////////////////////// 
    for (int i = 0; i < list.size(); i++) { 

     for (int j = i + 1; j < list.size(); j++) { 


      int percent = StringSimilarity.simi(list.get(i), list.get(j)); 
      if (percent >= 50) { 

       str.append("ID " + i + ",ID " + j + "," + percent + " percent"); 
       str.append("\n"); 
      } 
     } 
    } 
    //////////////////////////////////////////////////////// 

    long end = System.currentTimeMillis(); 

    f.write(str.toString()); 

    System.out.println((end - start)/1000 + " second(s)"); 

    f.close(); 
    scanner.close(); 

} 

public static String getString(String s) { 
    Pattern pattern = Pattern.compile("[^a-z A-Z]"); 
    Matcher matcher = pattern.matcher(s); 
    String number = matcher.replaceAll(""); 
    return number; 
} 

이 데이터가 어떻게 보이는지의 샘플입니다 ..... 이름은에 저장됩니다. csv 파일이므로 파일을 읽고 목록에 이름을 저장했습니다.

이름, 성, 기타 NAME, 어머니의 이름

킹슬리, 겔, 벤, CICI

겔, 다니엘, 벤, 줄리

존 스미스, 켈리, 조

조셉 황갈색, chellie

조셉 황갈색 헤세 로드리게스, chellie

.... 등등 사람이 최소한 3 개의 이름을 가질 수 있습니다 ..... 앞서 말했듯이 프로그램은 이름이 얼마나 비슷한 지 확인하기 위해 ID 1과 ID 2를 비교할 때 "ben" 는 공통적이며 "eze"는 공통이므로 50 %의 유사성을가집니다. ID 4와 ID 5를 비교하면 유사도가 75 퍼센트입니다 ... ID 4에 세 번째 이름이 없더라도 공통 이름이 3 개이므로 ....

두 가지 for 루프를 사용하여 유사성 검사를하면 첫 번째 ID로 시작하여 나머지 90,000 개의 이름을 통해 확인하고 ID가 50 % 이상인 ID를 저장 한 후 다음 ID 2를 취하여 같은 작업을 수행합니다. .. 등등

+1

데이터베이스에는 종종 사용할 수있는 ** soundex ** 알고리즘이 있으며 최소한의 "동일한 소리가 나는"문자 그룹이 생성됩니다. –

+1

"x % 유사성"은 어떻게 정의됩니까? – mm759

+0

* * Joseph * * Joe *와 (과) 유사합니까? 어때요 * Kady *와 * Catie *? 비슷한 철자가있는 이름 뒤에 만 있습니까, 아니면 비슷한 소리를내는 이름 뒤에 있습니까, 아니면 별명이나 별칭이 될 가능성이 있습니까? – jxh

답변

0

귀하의 알고리즘은 유사성에 대해 O (n^2)입니다. 가장 빠른 방법은 하나의 목록을 스캔하여 그 목록의 값을 키 맵으로 해시 맵에 보관하는 것입니다. 두 번째 목록을 스캔 한 다음 해당 요소가 이미 hashmap에 있는지 확인하십시오. 이렇게하면 더 빠르게 작동합니다.

+0

문제는 질문이 비슷한 이름과 동등한 이름에 관한 것이 아니라는 것입니다. – mm759

+0

두 번째 목록은 무엇입니까? hashmap에 어떤 값을 저장합니까? – serhiyb

+0

질문은 "이미 유사성을 확인하는 알고리즘이 있지만 전체 목록을 반복하는 데 많은 시간이 걸립니다. 누군가가 이름을 비교하는 빠른 알고리즘을 사용할 수 있습니까?" – Sakalya

0

많은 문자열 일치 알고리즘이 있으며 이미 SO에서 수행 된 많은 토론이 있습니다. 이를 통해

이동 https://stackoverflow.com/questions/955110/similarity-string-comparison-in-java

+0

이 질문의 도전 과제는 각 단어를 서로 비교할 필요없이 최소한의 유사성으로 쌍을 찾는 것입니다. – mm759

+0

@ mm759 여기에서 논의 된 알고리즘은 단일 문자 편집의 최소 수, 즉 삭제, 삽입 또는 대체를 비교하므로 이미 최적화되었지만 요구 사항에 관한 한 알고리즘 최적화를 통해 추가 최적화가 수행 될 수 있습니다. –

0

당신은 또한 아주 쉽게 IMHO이 작업을 병렬화 할 수있는 연결합니다. 동시에 하나 이상의 유사성을 계산하십시오. 그것은 알고리즘의 시간 복잡성을 개선하지는 못하지만 아무것도하지 않는 것보다 낫습니다. 6 11에서 편지가 다른 경우, 단순히 이미 반환 0

한 약간의 개선이 모두 StringBuilder를 사용하지하는 것과 일치를 이미 건너 발견 할 말 : :-)

+0

n^2 알고리즘을 병렬 처리하는 것은 n이 매우 커지면 대개 도움이되지 않습니다. 그의 경우에는 그는 80 억 회의 순서로 이야기하고 있습니다. 물론 네 개의 코어를 사용할 수 있으며 거의 ​​네 배 빠른 속도로 수행 할 수 있지만 더 나은 알고리즘을 사용하면 단일 코어에서 400 배 빠른 속도를 낼 수 있습니다. –

+0

예, 전적으로 동의합니다. 그러나 실제로 말했듯이, 실제로는 병렬 처리가 주목할 가치가 있습니다. – Firzen

2

simularity 기능이 최적으로 가정 . A ≈ B ∧ B ≈ C ∧ A ≉ C 일 수 있으므로 일부 일치가 손실 될 수 있으므로 약간 중요합니다.

final List<String> list = ... 
IntStream.range(0, list.size()) 
    .parallelStream() 
    .map(i -> ... 
    ... 

을하지만 그 차의 복잡성 아무 것도 변경하지 않을 것이다 :

Charset charset = StandardCharsets.ISO_8859_1; // Better UTF_8 

Path inputPath = Paths.get("names.txt"); 
List<String> list = Files.readAllLines(inputPath, charset); 

Path outputPath = Paths.get("output.txt"); 
try (PrintWriter out = new PrintWriter(Files.newBufferedWriter(path, charset))) { 

    int n = list.size(); 
    for (int i = 0; i < n; ++i) { 
     list.set(i, normalize(list.get(i))); 
    } 

    for (int i = 0; i < n; ++i) { 
     String ithWord = list.get(i); 
     for (int j = i + 1; j < n; ++j) { 
      String jthWord = list.get(j); 
      if (jthWord != null) { 
       int perc = similarity(ithWord, list.get(j)); 
       if (similarity >= 50) { 
        out.printf("ID %d,ID %d,%d percent or greater%n", i, j, perc); 
        list.set(j, null); // Skip it for other i 
       } 
      } 
     } 
    } 
} 

하나는 자바 (8)의 병렬 처리를 사용할 수 있습니다.

목록을 정렬하고 i 번째 단어에서 90 % 범위에있는 모든 접두사를 파생시키는 데 도움이되는 것은 무엇입니까? 불행한 것은 50 %로 실현 가능하지 않습니다 (n/2 이상).

비슷한 소리와 같은 다른 요구 사항을 요구할 수 있으며 최대 3 건의 오타가있을 수 있습니다. 또는 밤에 실행하십시오.

1

질문의 저자의 다음과 같은 코멘트가 중요하다 유사성으로

나는 ........ 존 스미스, 조, 케니와 존 스미스, 왕이, 켈리가 의미 공통 이름이 두 개 있기 때문에 50 %의 유사성이 있습니다. 이름이 3 개인 경우 75 %, 이름이 4 개인 경우는 100 %입니다.

지도 기반 접근 방식은 Sakalya가 이미 제안한대로 사용됩니다. 저는 HashMap을 키 집합의 이름과 값의 집합으로 사용하도록 제안합니다. 매핑 예를 들어 수 :

{"Jon", "Smith"} -> {"Jon, Smith, Joe, kenny", "Jon, Smith, king, kelly"} 

지도를 채우는 아이디어는, 각각의 이름을 모든 이름 부분을 포함하는 세트를 작성하고도 (빈 세트 제외)이 세트의 모든 부분 집합을 만드는 것입니다. 당신은 이름이있는 경우 "Jon, Smith, Joe, kenny" 세트는 다음과 같습니다

{"Jon"}, {"Smith"}, {"Joe"}, {"kenny"}, 
{"Jon", "Smith"}, {"Jon", "Joe"}, {"Jon", "kenny"}, {"Smith", "Joe"}, {"Smith", "kenny"}, {"Joe", "kenny"}, 
{"Jon", "Smith", "Joe"}, {"Jon", "Smith", "kenny"}, {"Jon", "Joe", "kenny"}, {"Smith", "Joe", "kenny"} 
{"Jon", "Smith", "Joe", "kenny"} 

이름은 키로 설정 한 각의지도에 가치 요소로 추가 할 수 있습니다. 이것은 각 이름에 대해 수행되어야합니다.

지도를 채운 후에는 각 이름을 다시 반복해야합니다. 하나는 이름의 부분 집합을 다시 만들어야합니다. 아이디어는 공통점이있는 다른 이름을 찾는 것입니다. 최소한의 크기를 가진 세트 만이 관련성이 있으므로 세트를 공유하는 다른 이름은> 50 %의 유사성을가집니다. 이 이름을 찾는 것은 관련 세트별로지도를 쿼리하여 수행 할 수 있습니다.

아무 것도 놓치지 않으면 복잡성 (시간뿐 아니라 공백도)은 이름 수와 관련하여 선형입니다. 이름의 최대 부분 수는 상수로 가정됩니다. n 부품 이름의 부품 세트의 수 2^n-1 (CP '멱 집합 ".)이다

  • 1 부 1
  • 2 부 : 3
  • 3 부 : 7
  • 4 부 : 15
  • 5 부 : 31
  • 6 부 63
  • 부 7 : 127

공간 요구 사항은 질문에 속한 알고리즘보다 높지만 일반적인 데스크톱 컴퓨터에서는 여전히 문제가되지 않습니다. 각 이름에 평균 20 개의 집합이 있고 각 집합에 40 바이트가 필요하다고 가정 해 보겠습니다. 이 경우 필요한 공간은 90,000*20*40 = 72,000,000 바이트입니다.공간 요구 사항은 String.intern()과 함께 문자열 풀을 사용하여 줄일 수 있습니다.

+0

이것을 구현하는 방법에 대한 샘플 코드를 얻을 수 있습니까? – kuebano

+0

기본 작업은 이름의 모든 부분을 포함하는 집합에서 이름 부분 집합을 만드는 것입니다. 여기에 예제가 나와 있습니다 : http://stackoverflow.com/questions/1670862/oba- tion-a-powerset-of-a-set-in-java. – mm759

관련 문제