# 34: F# で素因数分解(Exercism)

前回の投稿からしばらく間が空いてしまいましたが、再びプログラミングのことについて書いていこうと思います。ログを残しておけば結果的に自分にとって役に立ちますからね。

最近 Exercism というプログラミング学習サービスを利用し始めました。メジャーなものからマイナーなもの(Emacs Lisp とか!)まで、現時点で 55 のプログラミング言語のコース(track)があり、充実しています。

様々なお題(exercise)があり、順番に解いていくことでその言語の文法やイディオムを学べるようになっている、日本でいう Progate のような感じですね。

特徴としては、

  • 独自の CLI ツールがあり、ローカル環境で Exercise のコードならびにテストコードをダウンロードしてきて、編集して、投稿することができる。
  • 自分の解答を投稿したあとは他者の解答を参照することができる。
  • メンター制度があり、その言語について詳しい人からアドバイスを受けることができる。

というものがあります。

ひとまず今は F# track をやっています。

やったこと(Done)

素因数分解の exercise をやっていました。

与えられた数(int64)を素因数分解した結果を配列で返すというもの。

素数かどうか

まず素数かどうかを判定する関数 isPrime を書きます。

open System

// 素数かどうか判定する
let isPrime num =
    let sqrt (n: int64) = float n |> Math.Sqrt |> int64

    match num with
    | 0L
    | 1L -> false
    | 2L -> true
    | n -> List.forall (fun x -> n % x <> 0L) [ 2L .. (sqrt n) ]

素数とは「1 とその数自身以外では割り切れない正の整数」だと思いますので、[ n .. m] の記法で list を作って全てにおいて割り切れない(剰余が 0 ではない)ことを確認する、という処理です。

パターンマッチで 0, 1, 2 はベタ書きして、3 以上について一般化して書きました。

n 以下の素数を列挙

// n 以下の素数のリストを得る
let primeNumbers number = [ 0L .. number ] |> List.filter isPrime

List.filter を使うだけ。

素因数分解の結果をリストとして得る

// 素因数分解の結果をリストで返す
let factors (number: int64) =
    let results = []
    let primes = primeNumbers number

    let rec devider num (devisor: int64) (results: int list) =
        // 割られる数 num が割る数 devisor で割り切れたら
        if num % devisor = 0L then
            // その割る数 devisor を results に加える
            let newResults = (int devisor) :: results
            // さらに同じ数で割ってみる
            devider (num / devisor) devisor newResults
        else
            List.sort results

    List.fold (fun lst x -> devider number x lst) results primes

一見これでよさそうだと思ったのですが、テストの最後でめちゃくちゃ大きい数が渡されるものがあって、それがエラーになってしまいます。

わかったこと(Fact)

C# だと LINQAny() というメソッドがありますが、それと同様のものがあるか探したところ、逆のロジックのものがありました。

list の全てに対して評価式が true を返すかどうかをチェックする List.forall です。

> List.forall;;
val it : (('a -> bool) -> 'a list -> bool)

The predicate is applied to the elements of the input list. If any application returns false then the overall result is false and no further elements are tested. Otherwise, true is returned.

途中で false を返すものがあったらそれ以降の要素は走査しない、ということなので効率もよさそうです。