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

Nia-TN-SDF-A2

こんにちはー!ニアです。

今回は、F#でfor文を一切使用せずにカレンダープログラミングをしていこうと思います。

1. [Challenge] for in文を一切使用せずにカレンダープログラミング

ルール:

  1. https://gist.github.com/Nia-TN1012/c3b6002fc9dba0af15c6611ba519e807」の「Calender.fsx」と同じようなカレンダーを出力するプログラムをコードせよ
  2. 但し、for文(for toやfor inなど)は使用しない

という条件の下でプログラミングしていきます。

リスト生成時のfor in文をどうするか

その「Calender.fsx」(このブログでは「Calendar6.fsx」(F#でカレンダープログラミングをやってみました(その4・FINAL)より))を以下に示します。

open System
    
// 現在の日付を取得し、当月1日の曜日と末日を求めます。
DateTime.Now |> fun now -> ( now.AddDays( float( 1 - now.Day ) ).DayOfWeek |> int, DateTime.DaysInMonth( now.Year, now.Month ) )
    
// 範囲を[( - 1日の曜日 + 1 )..末日]にし、要素が負の時に空白を、そうでなければ日付を文字列に変換します。
// こうすることで1日の曜日に合わせてオフセットすることができます。
// また、要素の位置が7の倍数なら、改行記号も付け足します。
|> fun( prePad, lastDay ) -> [ for curDay in -prePad + 1..lastDay -> if curDay < 1 then "   " else sprintf ( if ( curDay + prePad ) % 7 = 0 || curDay = lastDay then "%3d\n" else "%3d" ) curDay ]
   
// イテレーターで各要素を出力します。
|> Seq.iter( fun cal -> printf "%s" cal )

9行目のfor in文の代替となる方法として、「-prePad + 1」から「lastDay」までのシーケンスをSeq.mapi関数によって日付リストを作成するという方法を思いつきました。

open System
    
// 現在の日付を取得し、当月1日の曜日と末日を求めます。
DateTime.Now |> fun now -> ( now.AddDays( float( 1 - now.Day ) ).DayOfWeek |> int, DateTime.DaysInMonth( now.Year, now.Month ) )
    
// 範囲を[( - 1日の曜日 + 1 )..末日]にし、要素が負の時に空白を、そうでなければ日付を文字列に変換します。
// こうすることで1日の曜日に合わせてオフセットすることができます。
// また、シーケンスのインデックス値の7の余剰が6もしくは末尾の要素なら、改行記号も付け足します。
|> fun( prePad, lastDay ) -> [ -prePad + 1..lastDay ] |> Seq.mapi( fun i curDay -> if curDay < 1 then "   " else sprintf ( if i % 7 = 6 || curDay = lastDay then "%3d\n" else "%3d" ) curDay )
   
// イテレーターで各要素を出力します。
|> Seq.iter( fun cal -> printf "%s" cal )

9行目において、1日の曜日の値(prePad)と月末日(lastDay)のタプルから「-prePad + 1」から「lastDay」までのシーケンスを生成し、それをSeq.mapi関数に渡します。

Seq.mapi関数で各要素に対する処理では、日付の値(curDay)の正負及び土曜日もしくは月末日を条件判断し、カレンダー用日付リストを生成します。

あとはSeq.iter関数で各要素を出力するだけで出来上がります。

「Calendar6.fsx(GitHub内:Calendar.fsx)」とほぼ同じ動作をしますが、日付リストの生成時の土曜日の条件判断において、あちらでは日付の値(curDay)と1日の曜日の値(prePad)の和を利用しているのに対し、こちらではSeq.mapi関数に指定したシーケンスのインデックス値を利用しています。なお、インデックス値は0から始まるので、7の余剰が6の時が土曜日となります。

CalendarFSI5

2. 最小コード量はどれくらい?

「Calendar7.fsx」と同じアルゴリズムの最小コードを以下に示します。

open System;DateTime.Now|>fun n->(n.AddDays(float(1-n.Day)).DayOfWeek|>int,DateTime.DaysInMonth(n.Year,n.Month))|>fun(p,l)->[-p+1..l]|>Seq.mapi( fun i d->if d<1 then"   "else sprintf(if i%7=6||d=l then"%3d\n"else"%3d")d)|>Seq.iter(fun d->printf"%s"d);0

コード量としては252バイトと、#カレンダープログラミングに採用されたコード「CalendarMin.fsx(GitHub内:同ファイル名)」(コード量:244バイト)より少し多いかな。

3. おわりに

これは、F#のカレンダープログラミングの開発記事をまとめている時にとっさに思いついたネタですが、いざ蓋を開けてみれば、for in文がSeq.mapi関数に変わっただけで動作が同じというオチになりましたw(当初は劇的に変化するのかなと思ったのだけど・・・)

でも、今回のようにわざと条件を付けてプログラミングするのも中々面白いと思います。

[END]

コメント

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