エンタープライズギークス (Enterprise Geeks)

企業システムの企画・開発に携わる技術者集団のブログです。開発言語やフレームワークなどアプリケーション開発に関する各種情報を発信しています。ウルシステムズのエンジニア有志が運営しています。

Java8 Stream APIを使ったピタゴラス数生成処理

先日の記事では、Stream APIの基本的な仕組みを書いた。今回はサンプルコードとして、ピタゴラス数を生成する処理をStreamで書いてみる。

ピタゴラス数とは、三平方の定理でおなじみの
a * a + b * b = c * c 
を満たす自然数の組のことだ。
最初のピタゴラス数は(3,4,5)で、(6,8,10)のような倍数となる組み合わせは除外する。

このロジックは、 a < b < c を前提として、
1. 最初に自然数cを生成し、
2. 各cの値について、c未満の自然数bを生成して、
3. 同様にb未満の数aを生成する。

候補となる3つの数字の組み合わせを生成した後で、
4. 数a,b,cについて条件を満たす組み合わせのみを抽出する
という流れで実現できる。

コードは以下のようになる。

import java.util.stream.IntStream;
import java.util.stream.Stream;

public class PitagorasNumberGenerator {
    public static void main(String[] args) {
       // ピタゴラス数を3つ表示する。
       generate().limit(3).forEach(System.out::println);
       // 100番目のピタゴラス数を表示する。
       generate().skip(99).limit(1).forEach(System.out::println);
        
    }
    /** ピタゴラス数生成 */
    public static Stream<String> generate() {
        return num(1) // cを無限数として生成。
                .flatMap(c -> num(1).limit(c) // 各数ごとに、1からc-1までの数を生成してbとする。
                    .flatMap(b -> num(1).limit(b) // 同様にb未満の数を生成してaとする。
                        .filter(a -> isCoprime(a, b)) // a,bだけて互いに素な数だけ抽出。
                        .filter(a -> isPitagorasNumber(a, b, c)) // ピタゴラス数の条件を満たすものを抽出
                        .map(a -> String.format("(%d,%d,%d)", a,b,c)))); // 最後はStringに変換。
    }
    
    /** 無限数生成 */
    public static Stream<Integer> num(Integer init) {
        return  IntStream.iterate(init, x -> x + 1).boxed();
    }
    /** 互いに素であるかの判定 */
    private static boolean isCoprime(int a, int b) {
        if (b == 0) return false;
        if (b == 1) return true;
        if (b > a) return isCoprime(b, a);
        return isCoprime(b, a % b);
    }
    /** ピタゴラス数であるか判定 */
    private static boolean isPitagorasNumber(int a, int b, int c) {
        return c * c == a * a + b * b;
    }
}

補足

num() メソッドは無限に繰り返される数を生成する。(num().forEach,,, などとすると無限ループに陥る。)
num() に中間操作を適用して作成された generate() メソッドの戻り値も無限であることに注意。

このようにStream APIを使うことで、本来無限であるはずのピタゴラス数を自然に表現できる。
呼び出し側では、件数制限の終端操作を適用する必要がある。
これを手続き型のコードで記述しようとすると、無限ループの中で条件が一致した場合にbreakするような判定処理にしなければならない。

[前多 賢太郎]