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

Myoga-SB-SDF-A2

Hello~! ミョウガです!

今回は、関数型言語のF#でカレンダーを出力するプログラムを作成した時のお話です。

1. F#でカレンダーを出力してみました

F#では.NET Frameworkの機能が扱えます。ということで、日付の取得と計算にはDateTime構造体の機能を利用していきました。

open System;  // DateTime型を利用するにはこの文が必要です。
 
[<EntryPoint>]
let main argv =
 
    // 現在の日付を取得し、当月1日の曜日と末日を求めます。
    let now = DateTime.Today
    let firstWeek = int( DateTime( now.Year, now.Month, 1 ).DayOfWeek )
    let lastDay = DateTime.DaysInMonth( now.Year, now.Month )

    // 1日の曜日に合わせてオフセットします。 
    for i = 1 to firstWeek do printf "   " done
    // カレンダーを出力します。
    for day in [1..lastDay] do
        // 日付を出力します。
        printf "%3d" day
        // 土曜日もしくは月末日を出力したら、改行します。
        if ( day + firstWeek ) % 7 = 0 then printfn ""
        else if ( day = lastDay ) then printfn ""
    done
 
    0

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

現在日の取得と1日目の曜日の取得までは前回までと同じですが、今回は月末の日をDaysInMonthメソッドで取得してfor in文でカレンダーを出力します。

12行目ではfor to文で1日の曜日に合わせてオフセットし、14行目からfor in文を使ってカレンダーを出力します。

for in文のinの後に指定する式には、IEnumerable インターフェイスを継承したデータ型(配列やリストなど)を使用することができます。ここでは1から月末日までの値を持つリスト(シーケンス)を指定するのですが、F#では「..」演算子を使って

[ 1..(月末日) ]

と入れるだけで簡単に生成することができます。なお、大括弧「[]」は省略することができます。

シーケンスはこのようにして作られますよ!

あとはループ内で1日から日付を出力し、土曜日または月末日の出力後に改行するだけでカレンダーの出来上がりです。日付出力にはprintf関数を使ってフォーマット指定を「%3d」とし、日付同士でスペースを空けつつ、1~9日を右寄せします。あら、C言語やC++のprintf関数と使い方がほぼ同じです。なお、printfn関数はコンソール画面に文字列を出力した後に改行します。

(※もちろん、printf関数やprintfn関数の代わりに、.NET FrameworkのConsole.WirteメソッドやConsole.WirteLineメソッドでもOKです)

2. 2つのループを1つに纏めてみました

先ほどのプログラムにて、12行目の1日の曜日合わせのパディング処理を14~20行目の日付出力の処理のfor in文に入れてみました。

open System;

[<EntryPoint>]
let main argv =

    // 現在の日付を取得し、当月1日の曜日と末日を求めます。
    let now = DateTime.Today
    let prePad = int( DateTime( now.Year, now.Month, 1 ).DayOfWeek )
    let lastDay = DateTime.DaysInMonth( now.Year, now.Month )
    
    // カレンダーを出力します。
    for curDay in [( -prePad + 1 )..lastDay] do
        // 範囲を[( - 1日の曜日 + 1 )..末日]にし、要素が負の時に空白を出力します。
        // こうすることで1日の曜日に合わせてオフセットすることができます。
        if curDay > 0 then printf "%3d" curDay else printf "   "
        if ( curDay + prePad ) % 7 = 0 || curDay = lastDay then printfn ""
    done

    0

※このプログラムはhttps://gist.github.com/Nia-TN1012/b58e951b626f9c9b3ac0515acfb651f3でも公開されています(※GitHubアカウントの統合のため、ニア側のGistに移動しました)。
また、「#カレンダープログラミング プチコンテスト 2014」ピックアップ Vol. 3」にてピックアップされました。
(※カレンダープログラミング当時、自分のアカウントをMyogaとNiaに分離する前でしたので、旧名の「Myoga S. Tomonaka」になっています。)

ここで重要なのが、for in文のinの後に指定するシーケンスの生成式で、範囲を[( – 1日の曜日 + 1 )..末日]にし、要素(ここではcurDay)が負の値や0の時、半角スペース3文字を出力するようにすることです。(1日の曜日が日曜日なら、シーケンスの生成式は[1..(月末日)] になります。)

cal-fs2

こうすることで、1つの繰り返し文だけでカレンダーを出力することができました。

3. F#のカレンダープログラミングはまだまだ続く…

F#でカレンダープログラミングをしていた時、言語リファレンスを眺めていたら、リストやシーケンスの結合処理やパイプライン処理など色々な機能があることを見つけました。

次回以降ではこれらの機能を活用した時のお話をしたいと思います。

[END]

コメント

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