シクシク素数列 Advent Calendar 2018 Java編

この記事は[「シクシク素数列 Advent Calendar 2018」2日目](https://qiita.com/advent-calendar/2018/4949prime-series)になります。

詳細はアドベントカレンダーの該当ページを見ていただくとして、ざっくり問題内容をまとめておくと「100以下の正の整数Nがあたえられたとき、N番目までの"シクシク素数"を求め、カンマ区切りで出力しなさい」というもの。"シクシク素数"とは数値に4または9を素数を指しています。

単純な問題なので、解法はいろいろあると思いますが、とりあえずは以下のような外部仕様を満たすAPIがあれば、これを組み合わせて、問題の答えが出せそうです。

1. `hasFourOrNine`: 自然数xが引数として与えられたとき、xに4または9が含まれている場合はtrue、それ以外はfalseを返す。
2. `isPrime`: 自然数xが引数として与えられたとき、xが素数ならばtrue、それ以外はfalseを返す。
3. `solve`: 100以下の自然数nが引数として与えられたとき、n番目までの"シクシク素数"をカンマ区切りで連結した文字列を返す。

さて実装に移っていくわけですが、Java6やJava7あたりのやや古いバージョンのJavaであれば、以下のように書いていたはずです(もっとも「やや古い」とはいうものの、Java製のシステムは概して長持ちなので、このぐらいの古さのバージョンであれば、現役で動いているものも多いと思います……)

```java:Main1.java

public class Main1 {
    public static void main(String[] args) {
        int n = 100;
        System.out.println(solve(n));
    }
    
    public static String solve(int n) {
        StringBuilder answer = new StringBuilder();
        for (int x = 1; n > 0; x++) {
            if (isPrime(x) && hasFourOrNine(x)) {
                answer.append(x);
                if (--n > 0) {
                    answer.append(",");
                }
            }
        }
        return answer.toString();
    }
    
    public static boolean hasFourOrNine(int x) {
        while (x > 0) {
            int i = x % 10;
            if (i == 4 || i == 9) {
                return true;
            }
            x /= 10;
        }
        return false;
    }
    
    public static boolean isPrime(int x) {
        if (x < 2) {
            return false;
        }
        
        for (int i = 2; i <= Math.sqrt(x); i++) {
            if (x % i == 0) {
                return false;
            }
        }
        return true;
    }
}
```

上記の例は手続き型プログラミングの色合いが強く、全体的な見た目としては縦長になります。では外部仕様を変更することなく、今日現在のJavaの最新バージョンである11らしく書き換えてみましょう (もっともJava11らしい機能はまったく使っていませんが……)

```java:Main2.java
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class Main2 {
    public static void main(String[] args) {
        int n = 100;
        System.out.println(solve(n));
    }
    
    public static String solve(int n) {
        return IntStream.iterate(1, x -> x + 1)
                        .filter(Main2::isPrime)
                        .filter(Main2::hasFourOrNine)
                        .mapToObj(String::valueOf)
                        .limit(n)
                        .collect(Collectors.joining(","));
    }
    
    public static boolean hasFourOrNine(int x) {
        return IntStream.iterate(x, i -> i > 0, i -> i / 10)
                        .map(i -> i % 10)
                        .anyMatch(i -> i == 4 || i == 9);
    }
    
    public static boolean isPrime(int x) {
        return x >= 2 && IntStream.rangeClosed(2, (int)Math.sqrt(x)).allMatch(i -> x % i != 0);
    }
}
```

Stream APIやラムダ式を多用することで、縦の長さはぐっと縮まり、全体として横長のコードが出来上がりました。関数型プログラミングといえるほどではありませんが、少なくともそのパラダイムの一部が導入されています。

プログラミングをするうえで大事にしたいのが「可読性」--ですが、この程度の長さだと可読性うんぬんという感じではないですね……。ただ個人的に書いていて楽しい・気持ちいいのは後者。メソッドチェーンは癖になる(´・ω・｀)

最後にn=100の場合の問題の解答を以下に示します。ただし問題の指定では「カンマ区切り」でしたが、読みやすさの都合上、以下では「改行区切り」で示します。ご了承ください。

```
19
29
41
43
47
59
79
89
97
109
139
149
179
191
193
197
199
229
239
241
269
293
347
349
359
379
389
397
401
409
419
421
431
433
439
443
449
457
461
463
467
479
487
491
499
509
541
547
569
593
599
619
641
643
647
659
691
709
719
739
743
769
797
809
829
839
859
907
911
919
929
937
941
947
953
967
971
977
983
991
997
1009
1019
1039
1049
1069
1091
1093
1097
1109
1129
1193
1229
1249
1259
1279
1289
1291
1297
1319
```