forやifで書くアレをStream APIで書く
前回の続編です。
前回の記事↓
Stream APIにチャレンジ! - Java EE 事始め!
Java SE 8ローンチイベントでの@cero_tさんの発表の中で、新しい定石として
「forやwhileを見かけたらStream APIへの置き換えを考える」
というのがありました。今回はこれにチャレンジしようと思います。
1~20までの偶数のみの合計を求める(答は110)
Java 7までの書き方
int sum1 = 0; for (int i = 1; i <= 20; i++) { if (i % 2 == 0) { sum1 += i; } } System.out.println(sum1);
Java 8での書き方①
int sum2 = IntStream.rangeClosed(1, 20) .filter(i -> i % 2 == 0) .sum(); System.out.println(sum2);
IntStream.rangeClosed(1, 20)で、1,2,3,…,19,20というIntStreamを生成し、filter(i -> i % 2 == 0)で偶数のみ抽出し、sum()で合計を求めます。
Java 8での書き方②
int sum3 = IntStream.rangeClosed(1, 10) .map(i -> i * 2) .sum(); System.out.println(sum3);
1~10までのIntStreamを生成し、map(i -> i * 2)で各値を2倍に変換します。
Java 8での書き方③
int sum4 = IntStream.rangeClosed(1, 10) .map(i -> i * 2) .reduce(0, (i1, i2) -> i1 + i2); System.out.println(sum4);
和を求めるのにreduce()を使っています。
((((0 + 2) + 4) + 6) + 8)…+20)という計算をしています。
単純に合計を求めるだけならsum()でいいと思います。
後で出てくる階乗の計算などは、reduce()を使う必要があります。
1の階乗~10の階乗をすべて求める
Java 7までの書き方
for (int i = 1; i <= 10; i++) { int kaijo = 1; for (int j = 1; j <= i; j++) { kaijo *= j; } System.out.println(i + "! = " + kaijo); }
Java 8での書き方①
IntStream.rangeClosed(1, 10) .forEach(i -> { int kaijo = IntStream.rangeClosed(1, i).reduce(1, (i1, i2) -> i1 * i2); System.out.println(i + "! = " + kaijo); });
reduce()で階乗を計算しています。
forEach()使っちゃってるので、マサカリが飛んできそうなコードです。
Java 8での書き方②
IntStream.rangeClosed(1, 10) .map(i -> IntStream.rangeClosed(1, i).reduce(1, (i1, i2) -> i1 * i2)) .forEach(i -> System.out.println(i));
map()を利用して、Streamの各要素を階乗の値に変換しています。
ただ、これでは元の1,2,3,…というデータが消えてしまうので、「3! = 6」というような表示が出来ません。
Java 8での書き方③
IntStream.rangeClosed(1, 10) .boxed() // .mapToObj(i -> i) でも同様 .collect(Collectors.toMap(i -> i, i -> IntStream.rangeClosed(1, i).reduce(1, (i1, i2) -> i1 * i2))) .forEach((k, v) -> System.out.println(k + "! = " + v));
キーが整数、値がキーの階乗というMapに変換しました。
boxed()は、IntStreamをStream<Integer>に変換する中間処理です。
これだと、
1! = 1
2! = 2
…
という表示が出来ます。
Collectors.toMap()は、2つのラムダ式を引数にとります。
2つとも、ラムダの引数はストリームの各要素になります。
1つ目のラムダの戻り値がMapのキー、2つ目のラムダの戻り値がMapの値になります。
FizzBuzz
Java 7までの書き方
for (int i = 1; i <= 40; i++) { if (i % 15 == 0) { System.out.print("FizzBuzz"); } else if (i % 3 == 0) { System.out.print("Fizz"); } else if (i % 5 == 0) { System.out.print("Buzz"); } else { System.out.print(i); } System.out.print(","); }
お馴染みのコードだと思います。
Java 8での書き方①
IntStream.rangeClosed(1, 40) .forEach(i -> { if (i % 15 == 0) { System.out.print("FizzBuzz"); } else if (i % 3 == 0) { System.out.print("Fizz"); } else if (i % 5 == 0) { System.out.print("Buzz"); } else { System.out.print(i); } System.out.print(","); });
これはマサカリコードです。
1~100までの素数をすべて求める
Java 7までの書き方
List<Integer> result1 = new LinkedList<>(); for (int i = 2; i <= 100; i++) { boolean isPrime = true; for (int j = 2; j <= Math.sqrt(i); j++) { if (i % j == 0) { isPrime = false; break; } } if (isPrime) { result1.add(i); } } result1.forEach(i -> System.out.print(i + ","));
Java 8での書き方
List<Integer> source = IntStream.rangeClosed(2, 100) .boxed() .collect(Collectors.toCollection(LinkedList::new)); List<Integer> result2 = new LinkedList<>(); while (true) { int firstElement = source.get(0); if (firstElement > Math.sqrt(100)) { break; } result2.add(firstElement); source.removeIf(elem -> elem % firstElement == 0); } result2.addAll(source); result2.forEach(i -> System.out.print(i + ","));
「エラストテネスのふるい」というアルゴリズムを使ってみました。
エラトステネスの篩 - Wikipedia
ただし、まだwhile文が残っています。これをStream APIで書き換える方法がまだ思いついていません。
引き続き、宿題ということで…。