LoginSignup
5
5

More than 5 years have passed since last update.

StreamのStreamによるStreamのための素因数分解

Last updated at Posted at 2016-06-16

この記事の内容とか

見るからにあたまわるそうでキャッチ―なコピーがついていることからわかるとおり、かなりのネタ記事です。
肝心の内容ですけど、いろいろと使い勝手悪いことで有名なJava8のStream API、これを使ってワンラインで素因数分解のプログラムが書けるのかという、ほんとどうでもいい内容ですね!

ルールとか

この問題、Atomic系とか破壊的参照とか使うと、わりと簡単に解けます。
なので、これらを禁止し、しかも{}も使わずワンライナーなメソッドを作ります。
速度面は気にしちゃ駄目ですができるだけなんとかしようと努力はします。

まずは簡単なコードとか

private List<Integer>basicFaactorization(final int num){
    return IntStream
        .iterate(num
                ,i->i==1?1:i%2==0?i/2:i/
                    IntStream
                    .iterate(3,j->j+2)
                    .filter(j->i%j==0)
                    .findFirst()
                    .getAsInt())
        .limit((int)(Math.log(num)/Math.log(2))+1)
        .distinct()
        .boxed()
        .collect(Collectors.toList());
}

このコードは何をしているのか、どんな結果が得られるのかを解説します。
あっさり書くと「1のときは1、2で割り切れるなら2で割った値、そうでないなら最小の約数で割った値の配列を返す」。
たとえば32なら、[32,16,8,4,2]、12なら[12,6,3,1]が返ります。
とりあえず12のときの動作を。
まず、イテレータ初期値12に対して、12==1(false)->12%2==0(true)->return 12/2が実行されて6ができます。
次に6に対して6==1(false)->6%2==0(true)->return 6/2が実行されて3ができます。
次に3に対して3==1(false)->3%2==0(false)->IntStreamで3,5,7,9という奇数を作り、3をその数字で割って割り切れた値で最初に見つかったもの(=最小のもの)をreturnが実行されて1ができます。
以降1==1なので1が返り続けますが、このイテレータの最大長はlog2(num)+1=4.58...で4回まで。
よって、実際に生成される配列は12,6,3,1。
distinctは、いきなり素数入れてきたときに1,1,1,1,...みたいなのが出来ないためのおまじない。
この数列から何ができるかというと、N[]に対してN[i]/N[i+1]を計算すると素因数分解の答えになります。
実際に計算してみると、12/6,6/3,3/1->2,2,3で素因数分解できていることがわかるでしょう。

この配列の問題点とか

問題は、こういうN[i]/N[i+1]ってのをStreamで扱えないってこと(たぶん)!!
前値と後値から計算するメソッドにはreduceがあるのですけど、少なくともこいつだと無理。
これ以外だとデフォルトで用意されてるのは無いんじゃないかと思います。

さあ困りました。せっかく目の前に、それっぽい配列があるのに、素因数分解の値にできないじゃないですか!
実はここで1か月くらい思考停止していたのですが、ラムダのデフォルトinterface見てて、突然閃きました。

それで素因数分解コードとか

private List<Integer>primeFactorization(final int num){
    return Stream.iterate(IntStream.of(2,num).toArray(),i->i[0]==1?i:i[1]%2==0?IntStream.of(2,i[1]/2).toArray():IntStream.iterate(IntStream.iterate(i[0]==2?3:i[0],j->j+2).filter(j->i[1]%j==0).findFirst().getAsInt(),j->i[1]/j).limit(2).toArray()).limit((int)(Math.log(num)/Math.log(2))+1).skip(1).filter(i->i[0]>1).mapToInt(i->i[0]).boxed().collect(Collectors.toList());
}

ながっ!横にながっ!
正しく動きますが、これ見て何やってるかわかったら「バーと同じくらい、あたまおかしい」認定します。
先ほどの簡単なコードの説明を読むと多少は読めるかもしれませんけど、意味不明すぎますね。

ちょっと見やすくしてコメント入れてみます。

return Stream//{約数m,現在の値n}のint[]型Stream
    .iterate(
            IntStream.of(2,num).toArray()//初期値は{m=2,n=num}
            ,i->i[0]==1?i//約数が1なら変更しない
                    :i[1]%2==0?IntStream.of(2,i[1]/2).toArray()//値nが2で割れるなら{m=2,n=n/2}
                    :IntStream //そうでないとき
                    .iterate(IntStream//奇数の最小約数を求めるStream
                            .iterate(i[0]==2?3:i[0],j->j+2)//ショートカット前回の約数から開始(ただし2のときは3から開始)
                            .filter(j->i[1]%j==0)//nが割り切れる値を探して
                            .findFirst()//最初に見つかった数=最小約数
                            .getAsInt()//これがIntStream.iterateの第一要素
                            ,j->i[1]/j)//iterate悪用して(現在の値/約数(第一要素))を強引に作る
                    .limit(2).toArray()//limit使って無理矢理int[2]の配列にする。
    ).limit((int)(Math.log(num)/Math.log(2))+1)//2^y乗<num+1となる回数だけ試行すればいい。
    .skip(1)//嘘の仮初期値{m=2,n=num}を除去
    .filter(i->i[0]>1)//約数が1のものを除去
    .mapToInt(i->i[0])//int[]を約数に変換
    .boxed()
    .collect(Collectors.toList());

ようするに、約数とは別に現在の値を持つ配列をiterate操作することで、N[i]/N[i+1]を成り立たせているという鬼コード。
ちなみに12345くらいの計算でも結構時間がかかります。
このListをiterateする方法を使うとフィボナッチ数とかもワンラインで書けると思われます(たぶん)。

結論とか

「streamという手段のためには目的は選びません!」
beautiful streamer(造語)を目指す人は、そんなダメ人間になってはいけません。
あと、こんな面倒なことしなくてもワンラインで書けるぜ!って人がいらっしゃったら教えてください……

5
5
9

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
5