F#でカレンダープログラミングをやってみました(その3)

Myoga-SB-SDF-A2

Hello~! ミョウガです!

今回はF#のパイプライン処理を活用して、カレンダーを出力するプログラムを作成した時のお話です。

1. パイプライン処理とは?

ここでいうパイプライン処理とは、2つ以上の関数を連結して1つの繋がった処理にすることができます。

例えば、ある値に1を足してそれを2乗する処理はパイプライン処理(演算子は「|>」)を使って表すと以下のようになります。

let Add1 x = x + 1
let Square x = x ** 2
printfn "%d" Square ( Add1 11 )
↓

let Add1 x = x + 1
let Square x = x ** 2
11 |>  Add1 |> Square |> printfn "%d"

メリットとしては処理の流れが見やすくなることかな。

2. パイプライン処理を活用してみました。

では早速、前回のプログラム「Calendar.fs(GitHUb内: CalendarOld.fs)」の14行目のSeq.iteri関数にある、カレンダー用リスト生成処理をパイプライン演算子を使って、関数の前に移動させてみます。

[<EntryPoint>]
let main argv =

    // 現在の日付を取得し、当月1日の曜日と末日を求めます。
    let now = System.DateTime.Today
    let prePad = int( System.DateTime( now.Year, now.Month, 1 ).DayOfWeek )
    let lastDay = System.DateTime.DaysInMonth( now.Year, now.Month )


    // 1日のパディング部と日付(あらかじめ文字列に変換します)部のリストを連結し、カレンダー用リストを生成します。
    [ for i in 1..prePad -> "   " ] @ [ for d in 1..lastDay -> sprintf "%3d" d ]
    // カレンダーを出力します。
    // iはリストのインデックスであり、日曜始まりでです。dは日付もしくは空白スペースです。
    // iの7の余剰が6、即ち土曜日であるもしくは月末日であれば、改行も出力します。
    |> Seq.iteri ( fun i d -> printf ( if i % 7 = 6 || i + 1 = prePad + lastDay then "%s\n" else "%s" ) d )
         
    0

こうすることで、「カレンダー用のリストを作成して、日付を出力する」という処理の流れがより掴みやすくなった他、Seq.iteri関数呼び出しの部分がちょっとスッキリしました。

また、日付の文字列への変換処理では、.NET FrameworkのString.Formatメソッドの代わりに、F#のsprintf関数を使いました。

3. コレクションの各要素に適用する処理を簡素にしてみました。

Seq.iteri関数呼び出しの部分をさらに簡素に(各要素をただ出力するだけ)しようと思い、週末・月末日での改行記号追加の処理をSeq.iteri内の関数から独立させてみました。

[<EntryPoint>]
let main argv =

    // 現在の日付を取得し、当月1日の曜日と末日を求めます。
    let now = System.DateTime.Today
    let prePad = System.DateTime( now.Year, now.Month, 1 ).DayOfWeek |> int
    let lastDay = System.DateTime.DaysInMonth( now.Year, now.Month )

    // 1日のパディング部と日付(あらかじめ文字列に変換します)部のリストを連結し、カレンダー用リストを生成します。
    [ for i in 1..prePad -> "   " ] @ [ for d in 1..lastDay -> sprintf "%3d" d ]

    // iはリストのインデックスであり、日曜始まりでです。dは日付もしくは空白スペースです。
    // iの7の余剰が6、即ち土曜日であるもしくは月末日であれば、要素に改行を入れます。
    // マッピングを終えたら、イテレーターで各要素を出力します。
    |> Seq.mapi( fun i d -> if i % 7 = 6 || i + 1 = prePad + lastDay then d + "\n" else d ) |> Seq.iter( fun d -> printf "%s" d )

    0

※このプログラムはhttps://gist.github.com/Nia-TN1012/c3b6002fc9dba0af15c6611ba519e807でも公開されています(※GitHubアカウントの統合のため、ニア側のGistに移動しました)。

17行目のSeq.mapi関数でカレンダー用リストの各要素に対し、週末・月末日に改行記号を追加します。

日付の出力処理では、各要素をそのまま出力するだけになりますので、Seq.iter関数の代わりにSeq.iter関数を使います。

CalendarFSI3

4. 日付の文字列変換をショートコード化

前回までは日付の文字列変換処理にて、.NET FrameworkのString.Formatメソッドを使用していましたが、

プロ生ちゃんから「[ d.ToString().PadLeft( 3 ) ] の方が短くなる」というアドバイスをいただきました(※以下、「プロ生ちゃん提案法」とします)。

// dは日付の値です。

// 今までのコード
System.String.Format("{0,3}",d) // 31バイト
// ※open SystemでSystem名前空間をインポートした場合
String.Format("{0,3}",d)       // 24バイト

↓

// プロ生ちゃん提案法
d.ToString().PadLeft(3)        // 23バイト

String.Formatは静的メソッドのため、「System.String.Format」と完全な名前にするか、Systemをインポートする必要がありますが、ToStringとPadLeftメソッドはオブジェクトに対するメンバーなので、そのようなことは不要になります(しかも、変換処理だけを比較してみても、プロ生ちゃん提案法の方が短いです)。

さらに追及してみると・・・

もっとショートコード化できるかな・・・と、F#のリファレンスを巡回したところ、文字列への変換はF#のstring関数で出来ることに気付きました。

// dは日付の値です。

// プロ生ちゃん提案法
d.ToString().PadLeft(3)        // 23バイト

↓

// 文字列変換をF#のstring関数にした場合
string(d).PadLeft(3)           // 20バイト

さらに追求してみると、書式指定文字列への変換はF#のsprintf関数で出来ることに気付きました。

// dは日付の値です。

// 文字列変換をF#のstring関数にした場合
string(d).PadLeft(3)           // 20バイト

↓

// sprintf関数を使った場合
sprintf"%3d"d                 // 13バイト

なんとString.Formatメソッドを使っていた時より約半分のコード量でできます!

5. おわりに

次回は、F#のカレンダープログラムの更なるショートコード化にチャレンジした時のお話をしていきたいと思います。

[END]

コメント

タイトルとURLをコピーしました