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/Myoga1012/e4c9e921ed1ae635185b でも公開されています。

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メソッドの約半分のコード量でできるなんて・・・、sprintf関数さんマジぱないですっ!

5. おわりに

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

ではでは、See you♪

この記事をシェアする
Chronoir.netのRSSフィードを購読する

ミョウガ(Myoga)

Hello~! 「ミョウガ」です。よろしくお願いします!(≧▽≦)/ 主にC#/XAML/C++などをメインにプログラミングやアプリの開発をしています。好きな物はカフェラテとハーブティ、趣味は写真撮影と艦これです。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

*

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください