2017-05-13 15 views
1

問題のシナリオを説明しましょう。Java 8 - 要素をHashMapにマージするには

ファイルから一度に1行ずつ読んでいます。私は区切り記号を使用して行を分割し、各単語の頻度を数えてマップに保存します。

コードスニペット:私は次の行を読んだとき

Map<String, Integer> frequencyMap = new HashMap<>(); 
try { 
       fileReader = new FileReader(fileName); 
       BufferedReader bufferedReader = new BufferedReader(fileReader); 
       String line = ""; 
       while ((line = bufferedReader.readLine()) != null) { 
        frequencyMap = Arrays.stream(line.split(PHRASE_SEPARATOR)) 
                  .map(String::trim) 
                  .collect(Collectors.groupingBy(Function.identity(), Collectors.summingInt(e -> 1))); 
       } 
       bufferedReader.close(); 
      } catch (IOException e) { 
       e.printStackTrace(); 
      } 

はしかし、それは、既存の値にキーと値のペアをマージしません。ファイルから行を読み込んでいる間に、新しいキーと値のペアがマージされるようにするにはどうすればよいですか。

+0

あなたは各繰り返しで 'frequencyMap'を上書きしています。 –

+0

はい、そうです。同意する。新しいKey-Valueペアを追加する方法はありますか? –

+0

@RahulDevMishra問題は、追加したくないですが、周波数マップをマージしたいということです。次の行のエントリーは現在のエントリーを置き換えるべきではなく、マージしてください。たとえば、この行に2 "a"があり、マップにすでに5がある場合、 "a"の新しいエントリは2ではなく7になる必要があります。 – dasblinkenlight

答えて

3

あなたが行くようにカウントをマージする必要があるので、あなたは、文字列のストリームとしてファイルを読み込み、分割、およびこのように、単一の実行にマップする必要があります。

Map<String,Integer> frequencyMap = Files.lines(Paths.get(fileName)) 
    .map(line -> line.split(PHRASE_SEPARATOR)) 
    .flatMap(Arrays::stream) 
    .map(String::trim) 
    .collect(Collectors.groupingBy(Function.identity(), Collectors.summingInt(e -> 1))); 

または例外セーフなアプローチを使用します:あなたは、現在の行のマップを持っていたら

Map<String,Integer> frequencyMap = null; 
try (Stream<String> stream = Files.lines(Paths.get(fileName))) { 
    frequencyMap = stream 
     .map(line -> line.split(PHRASE_SEPARATOR)) 
     .flatMap(Arrays::stream) 
     .map(String::trim) 
     .collect(Collectors.groupingBy(Function.identity(), Collectors.summingInt(e -> 1))); 
} catch (IOException e) { 
    e.printStackTrace(); 
} 

Demo.

+0

これはまさに私が探していたものです。どうもありがとう。デモの場合は +1 –

+0

Upvoted、私は実際にこのアプローチは私のよりも優れていることをOPに説明していました –

1

あなたのコードはすべての反復でfrequencyMapを置き換えているため、それらはすべてカウントされません。

また、マップ操作の出力がリストの場合は、flatMapを使用して、そのリスト内のすべてのエントリをストリームのエントリとして追加できます。このタイプの一対多の関係を行うときに非常に役立ちます。このような何かがうまくいくかもしれない

は、それをテストする機会がなかった。

import java.io.IOException; 
import java.nio.file.Files; 
import java.nio.file.Paths; 
import java.util.Map; 
import java.util.function.Function; 
import java.util.stream.Collectors; 
import java.util.stream.Stream; 

public class FileReaderTest { 

public void main(String[] args) { 

    String fileName = args[0]; //sorry, getting lazy here... 

    try (Stream<String> stream = Files.lines(Paths.get(fileName))) { 
     Map<String, Long> frequencyMap = stream 
       .flatMap(line -> Stream.of(line.split(LINE_SEPARATOR))) 
       .map(String::trim) 
       .collect(Collectors.groupingBy(
         Function.identity(), 
         Collectors.counting())); 
    } catch (IOException e) { 
     e.printStackTrace(); 
    } 
} 

}

このコードの問題は、それが最終的にはメモリ内のファイル全体をロードすることです。小さなファイルの場合は大丈夫ですが、大きなファイルの場合は実装が異なります。

+1

これはファイル全体をメモリにロードしません。それどころか、一度に1行を読み込みます。 (私は確信していませんが、実際にはいくつかの行を読み込んでバッファを使いますが、完全なdetalisを認識するためにはコードを読む必要があります)。それにもかかわらず、私はそれがファイルの完全な内容をメモリにロードしないと確信しています。 –

+0

私は、ストリームの "収集"操作は、評価する前にストリーム内のすべての項目を必要とすると考えました。 [_ "ほとんどすべての場合、ターミナル操作は熱心で、データソースの走査とパイプラインの処理を完了してから戻ることができます。" _](https:// docs .oracle.com/javase/8/docs/api/java/util/stream/package-summary.html) Collectors.countingの実装はスレッドセーフな方法で内部カウンタを変更していますか? –

+1

ドキュメントから抜粋しても、 'collect'がストリーム全体をメモリに読み込むわけではありません。つまり、ほとんどの端末操作が呼び出されると、ストリームが消費されます( 'Files.lines'の場合、一度に1行ずつ消費されます)。実際、私は正しいのであなたの答えをupvotedしています。間違っているのはあなたの最後の声明です。 –

1

は、ファイル全体のための周波数をカウントマップでそれをマージする必要があります。

Map<String, Long> lineFrequencies = Arrays.stream(line.split(PHRASE_SEPARATOR)) 
    .map(String::trim) 
    .collect(Collectors.groupingBy(
     Function.identity(), 
     Collectors.counting())); 

// Merging of lineFrequencies into frequencyMap happens here 
lineFrequencies.forEach((k, v) -> frequencyMap.merge(k, v, Long::sum)); 

ここで私は存在しない値(で提供された値をマージする(この場合Long::sumで)提供される機能を使用していますMap.merge方法を、使用しました:だから、あなたのwhileループ内で、私はこれを行うだろうマップに指定されたキーのマッピングがすでに存在する場合)、または指定されたキーを指定された値にマップします(マップに指定されたキーのマッピングが含まれていない場合)。

このコードでは、frequencyMapMap<String, Integer>からMap<String, Long>に変更する必要があります。これはIMHOには影響しません。

+1

これはうまくいきました。ありがとうございました... –

+0

キーが存在しない場合はチェックし、次にキーが既に存在する場合は1で値を初期化してから値を取得し、インクリメント。 これは、各行に余分なマップを作成してから再度マージする必要はないということです。どう思いますか ? –

+0

@RahulDevMishra私はあなたの質問に正確に答えました。これは、テキストファイル全体の単語数を使って、1行の単語数をマップにマージする方法でした。あなたは既に各行の時間マップを作成していました。私がしたのは、それをあなたの 'frequencyMap'にマージすることでした。しかし、ファイルからすべての行をストリーミングし、各行を単語のストリームにフラットマップし、最終的に単語の数を 'frequencyMap'に集める方が良いと思います。これはdasblinkenlightがそれをした方法です。 –