2010-12-26 12 views
4

私は本を読んでいて、そこからいくつかの単語を削除しています。このループのパフォーマンスを向上させることはできますか?

Vector<String> pages = new Vector<String>(); // Contains about 1500 page, each page has about 1000 words. 
Vector<String> wordsToDelete = new Vector<String>(); // Contains about 50000 words. 

for(String page: pages) { 
    String pageInLowCase = page.toLowerCase(); 

    for(String wordToDelete: wordsToDelete) { 
     if(pageInLowCase.contains(wordToDelete)) 
      page = page.replaceAll("(?i)\\b" + wordToDelete + "\\b" , ""); 
    } 

    // Do some staff with the final page that does not take much time. 
} 

このコードを実行するために約3分かかります:私の問題は、例をプロセスに時間がかかる、と私はその性能より良い(少ない時間)を作りたいということです。もし私がのループをスキップしたらreplaceAll(...)私は2分以上を保存することができます。 高速なパフォーマンスで同じループを実行する方法はありますか?

+6

さらに悪い何が、このコードは効果がありません。実行後、ベクトルは変更されません。 –

+1

'(?i)'を使っているので、ページを小文字に変換する必要はありません。 – gdejohn

+0

FYI:https://secure.wikimedia.org/wikipedia/en/wiki/String_searching_algorithm – Bozho

答えて

5

最初に、contains(..)のチェックを取り除くことができます。不要なオーバーヘッドが追加されます。そして、これが当てはまらない時には、時には真実を返すでしょう。たとえば、ページに「ノット」しかない場合でも、「not」の場合はtrueが返されます。

別のもの - VectorArrayListに置き換えてください。

そしてKonradがコメントに示したように、あなたはベクトルを変更していません。 Stringは不変なので、オブジェクトを変更していません。 set(..)を使用して(繰り返しインデックスを維持する)必要があります。

+0

あなたは "not"/"knot"について正しいです。しかしcontains(...)ではオーバーヘッドは発生しません...逆に、削除する単語の1000は実際にページに存在しないので、この条件はreplaceAll(.. 。) 遅い。私が(...を含む)を省略すると、私の場合、プロセスは5分以上かかるでしょう。 – Brad

12

はい、ページを別の方法で処理できます。基本的な考え方は、ここで

for (String word : page) { 
    if (!forbiddenWords.contains(word)) { 
     pageResult.append(word); 
    } 
} 

を以下forbiddenWordsされるセットです。
また、for (String word : page)は、ページを単語のリストに構文解析するための略語です。結果に空白を追加することを忘れないでください(分かりやすくするために省略します)。

元のバージョンでの1ページの処理の複雑さは〜50000 * 1000でしたが、現在は1000までです。私は10分間の仕事から自分をそらすしたかったので

編集
は、ここで使用java.lang.StringBuilderコード:)

String text = "This is a bad word, and this is very bad, terrible word."; 
    Set<String> forbiddenWords = new HashSet<String>(Arrays.asList("bad", "terrible")); 

    text += "|"; // mark end of text 
    boolean readingWord = false; 
    StringBuilder currentWord = new StringBuilder(); 
    StringBuilder result = new StringBuilder(); 

    for (int pos = 0; pos < text.length(); ++pos) { 
     char c = text.charAt(pos); 
     if (readingWord) { 
      if (Character.isLetter(c)) { 
       currentWord.append(c); 
      } else { 
       // finished reading a word 
       readingWord = false; 
       if (!forbiddenWords.contains(currentWord.toString().toLowerCase())) { 
        result.append(currentWord); 
       } 

       result.append(c); 
      } 
     } else { 
      if (Character.isLetter(c)) { 
       // start reading a new word 
       readingWord = true; 
       currentWord.setLength(0); 
       currentWord.append(c); 
      } else { 
       // append punctuation marks and spaces to result immediately 
       result.append(c); 
      } 
     } 
    } 

    result.setLength(result.length() - 1); // remove end of text mark 
    System.out.println(result); 
+0

ニース。しかし、ホワイトスペースと句読点は考慮されていないと思います(またはそれはありますか?) – Bozho

+0

@Bozhoそうです、技術的な詳細は省略されています。時間の複雑さには影響しませんが、確かにコードは大きくなります。とにかく –

+1

+1。私は、これらの詳細を考え出すのは難しくありません:)たとえば、ページを分割して各句読点を「単語」として数え、各単語の後に空白を追加するようにすることができます。 – Bozho

0

です(単語がHashSetであるかどうかをチェックすることは一定の時間がかかります) - それは、特別に作成されています変更されたテキストの場合。

StringBuilder builder = new StringBuilder(page); 
for (String word: wordsToDelete) { 
    int position = 0; 
    int newpos = 0; 
    while ((newpos = builder.indexOf(word, position))>=0) { 
     builder.delete(position, position+word.length()); 
     position = newpos; 
    } 
} 

それはちょうどアイデアだ - それはワード境界

1

問題はあなたがループのための二重を持っているかどうかをチェックしません。これらは一般的にパフォーマンスが低く、x * yの性能と同等です。また、LowerCaseを呼び出してreplaceAllを呼び出すたびに文字列を変更することはできないため、新しい文字列を作成しています。だから、リスト内の各単語のページ全体を含むx * y個の文字列を作成しています。これは、正規表現でMULTI_LINEとCASE_INSENSITIVEオプションを使用することで回避できます。

これを1つのループに減らし、regexを使ってすべての単語を一度に置き換えることができます。ページを仮定

StringBuffer buffer = new StringBuffer(); 
    for (String word : wordsToDelete) { 
     if (buffer.length() != 0) { 
      buffer.append("|"); 
     } 
     buffer.append("(\\b"); 
     buffer.append(word); 
     buffer.append("\\b)"); 
    } 

    Pattern pattern = Pattern.compile(buffer.toString(), Pattern.CASE_INSENSITIVE | Pattern.MULTILINE); 

    List<String> newPageList = new ArrayList<String>(); 

    for (String page : pages) { 
     Matcher matcher = pattern.matcher(page); 
     String newPage = matcher.replaceAll(""); 
     newPageList.add(newPage); 
    } 
+0

私は\例:\\ b(word1 | word2 | word3)\\ b Pattern.compileは、それを実現するのに十分な時間で始めることができますか? –

+0

彼は何を望むかによって異なりますもしあなたが\ bを各単語につけていなければ、リスト "hello"、 "world"}は "helloworld"を置き換えます。\ bを置くと "helloworld"を置き換えずに "hello world " –

+0

私はこのソリューションを気に入っていますが、試してみましたが、まだ遅いです。作成したバッファーが大きすぎるので、この大きなパターンを各ページに適用すると時間がかかります。 – Brad

0

は独立しており、あなたの周りの複数のコアを持っている、とあなたが処理するために、ページの多くを持っている場合は、このループはまた、並列化することができます

final ArrayList<String> pages = ...; 
final Set<String> wordsToDelete = ...; 
final ExecutorService pageFrobber = Executors.newFixedThreadPool(8); //pick suitable size 
final List<Callable<String>> toFrobPages = new ArrayList<Callable<String>>(pages.size()); 

for(final String page: pages) { 
    toFrobPages.add(new Callable<String>() { 
     String call() { 
     return page.toLowerCase().replaceAll("(?i)\\b" + wordToDelete + "\\b" , ""); 
     } 
    }); 
} 

final List<Future<String>> frobbedPages = pageFrobber.executeAll(toFrobPages); 
// the above will block until all pages are processed 
// frobbedPages will contain a set of Future<String> which can be converted to strings 
// by calling get() 
関連する問題