タイダログ

もっと怠けますか? (y/n)

F# で基数変換をする

異なる n 個の記号を r 個並べたときの全ての組み合わせを知りたくなったので、基数変換のプログラムを書きました。

最近、特に数学に関する分野のことは、F# で書くことが多くなってきたと感じます。

Excel の BASE関数? よくわかんないなぁ。

実行環境

どうして?

「どうして基数変換で全ての組み合わせを作れるの?」という意味の質問でしたら、「N進法」について知れば解決です。

たとえば普段私たちが使っている「10進法」というのは「0123456789」の10種類の記号を順番に組み合わせて数を表す方法です。「2進法」では「01」の2種類の記号を、「16進法」では「0123456789ABCDEF」の16種類の記号を順番に組み合わせて数を表します。このように、N種類の記号の組み合わせで数を表す方法を「N進法」と呼び、Nのことを「基数」(radix) と呼びます。10進法で表現している数を2進法で表現しなおしたり、2進法の数を16進法にしたりすることを「基数変換」(radix conversion) と呼びます。

もうおわかりでしょうか。「N種類の記号を順番に組み合わせる」というN進法と、「異なる n 個の記号を r 個並べたときの全ての組み合わせを作る」という処理は、非常によく似ているわけです。試しに「0~9」の10種類の記号を3個並べたときの全ての組み合わせを考えるなら、000, 001, 002, ... , 997, 998, 999 という風に、0から103-1 = 999 まで10進法で数えるだけで済みます。5種類の記号を5つ並べる組み合わせなら、0から55-1 = 3124 まで、00000, 00001, 00002, 00003, 00004, 00010, 00011, ... , 44442, 44443, 44444 と5進法で数えるだけです。N進法は数を表しているわけですから、当然組み合わせに重複がありません。これが、全ての組み合わせの列挙に基数変換を使う理由です。

「どうしてわざわざそんなことするの?」という意味の質問でしたら、楽しいからです。

完成形

数字、アルファベット大文字、アルファベット小文字、ひらがな、カタカナ、全て使うことで、229進法まで対応しています。

Excel の BASE関数? 36進法までしか対応してないからね。

// 1つの整数を10進法から229進法に変換する関数
// int -> string
let radixConversionOneChar number =
    match number with
    | var when var <= 9 -> string number // 0-9
    | var when var <= 35 -> number - 10 + (int 'A') |> char |> string // A-Z
    | var when var <= 61 -> number - 36 + (int 'a') |> char |> string // a-z
    | var when var <= 144 -> number - 62 + ('ぁ' |> char |> int) |> char |> string // ぁ-ん
    | var when var <= 228 -> number - 145 + ('ァ' |> char |> int) |> char |> string // ァ-ヴ
    | _ -> "";;

// 指定した桁数でゼロ埋めする関数
// int -> string -> string
let padWithZero digit (text : string) =
    if digit <= text.Length then
        text
    else
        digit - text.Length // ゼロ埋めのゼロの個数を求める
        |> string
        |> (fun x -> (0).ToString("D" + x))
        |> (fun x -> x + text);; // 先頭にゼロをつける

// これを呼び出す
// int -> int -> int -> string
let radixConversion radix digit number =
    
    // 割り算を繰り返す再帰関数
    // int -> int -> string -> string
    let rec radixConversionRec number radix acc = 
        
        // 「整数 / 基数」の商
        let quotient = number / radix
        
        // 商を基数変換したもの
        let quotientOneChar = radixConversionOneChar quotient
        
        // 余りを基数変換したもの
        let remainderOneChar = number % radix |> radixConversionOneChar
        
        match quotient with
        | var when var < radix -> quotientOneChar + remainderOneChar + acc
        | _ -> radixConversionRec quotient radix (remainderOneChar + acc)
    
    // 基数変換したあとゼロ埋めする
    radixConversionRec number radix ""
    |> padWithZero digit;;

実行結果

radixConversion 62 10 106312;;
// 0000000Rei

radixConversion 229 3 8023389;;
// エヴァ

N進法から10進法へ

10進法からN進法に変換する関数は作りましたが、10進法に戻す関数は忘れていました。呪いはかけるだけで解けない荒地の魔女みたいになってしまった。まぁそのうち作るわよ。

新たに学んだこと

ゼロ埋めの関数内で、先頭につけるゼロの羅列を作るために intToString() メソッドを使おうとしたのですが、0.ToString("D" + x) としたらエラーが出ました(x には必要なゼロの桁数が渡っていて、D5 のような書式指定子を作っています)。苦し紛れに (0).ToString("D" + x) とすることで難を逃れましたが、String.replicate 関数を使ってもよかったなと思いました。

本当にしたかったこと

Wordle を楽して解きたかったです。

www.nytimes.com

その話は別の記事で。

結び

10進法から2進法や16進法への変換なんて神の御業かと思っていましたが、仕組みを勉強したことで自分でもできるようになりました。勉強したことを実践するのって楽しいですよね。

参考