hnagamin’s diary

競プロとか

関数品評会【KMCアドベントカレンダー7日目】

皆さんおはようございます! そしてお久しぶりです! KMC1回生のhnagaminです。
この記事はKMC Advent Calender 2014 7日目の記事です。昨日はnona7(nonamea774)さんの「不自由なYo からの脱却を求めて。【KMC アドベントカレンダー6日目】」でした。必読です。

今日は僕が参加しているKMC部内プロジェクトである「関数品評会」とその中で発表した一作品の解説を行います。

関数品評会とは

関数品評会はLISPプログラムを発表しあう会です。毎週LISPの諸方言で小さなプログラムを1つ以上書いて発表し、その設計の方針や関数命名、プログラム内で使われているオペレータの仕様などについて議論します。当初は激しいマサカリの投げ合い、発表者同士のデスマッチを期待していたのですがそのようなことは起こりませんでした。現状いたって平和な会です。
現在の発表者は2名(Common Lisp: 1名, Scheme: 1名)ですがきっと今後更に増えます。

作品例

第3回関数品評会で私が発表した「整数をローマ数字表記文字列にする」を紹介します。全文を貼ると記事が無限に縦長になるのでリンク先を参照してください。
何をするかは名前の通りです。1〜3999の整数をローマ数字表記に変換する関数です。



実際に処理を行うのはnumber->romanです。integer-refでnの各桁の値を取り出し、各々にsimplenumber->romanを適用して最後にstring-append(文字列の連接)で畳み込むという操作を行っています。

integer-refの実装を見てみましょう。

やっていることはとても単純で、n\%10^{i+1}-n\%10^iを返しているだけです。ただし\%は剰余を表します。

gosh> (integer-ref 1234 0)
4
gosh> (integer-ref 1234 1)
30
gosh> (integer-ref 1234 2)
200

といった動作をします。

それではsimplenumber->romanはどのようなことを行うのでしょうか。

中身はcondによって場合分けされています。

  • (a) nが0なら空文字列を返す。
  • (b) nが「four?的」であるなら(one n)と(one+ n)に自分を適用して連接して返す。
  • (c) nが「half<?的」であるなら(half n)と(half- n)に同じく自分を適用して連接する。
  • (d) 上記のいずれでもない場合、2パターンに分かれます。
    • (d-1) *number-roman-table*に対象が存在する場合それを返す。
    • (d-2) さもなければ(one n)と(one- n)に同じく(ry

一行一行述べるとこんな感じです。(a)は説明するまでもないとして、ほかの場合について説明します。

(c)


(c)から先に説明します。この場合分けは「VI, VII, VIII」など5より大きい数を「5+何か」で表現するルールのために存在します。

halfは整数n(i桁)をとって、5\times10^{i-1}を返します。half-はnから(half n)を引いたものを返します。なんかもう見たまんまですね。

(b)


ある整数がfour?的であるとは、大雑把に言うと整数が4っぽいまたは9っぽいことを表します。4は「IIII」ではなく「IV」ですし、9は「VIIII」ではなく「IX」です。このルールを表現するために(b)が存在します。
four?の実装は次の通りです。

41行目が「9っぽい」nにtrueを、42行目が「4っぽい」nにtrueを与えています。
つまり、

  • nが(half n)より小さい場合、nに(one n)を加えてnが(half n)以上になるとき(つまりnが「4っぽい」とき)はtrue
  • nが(half n)以上の場合、nから(half n)を引いた値が「4っぽい」とき(つまり「9っぽい」とき)はtrue

にtrueを返します。このへんの文章すごく読みづらい。算数の曲芸という感じがしますね。

(d)


だんだん説明することもなくなってきました。最後はelse節です。
この場合分けは(a),(b),(c)で拾われない数を表現するために存在します。「4,9(b),6,7,8(c)」以外の「1,2,3,5」ですね。
このうち「1,5」についてはテーブル*number-roman-table*を参照すればよいです(d-1)。それ以外は「1を何個か繰り返す」ことで表現できるので再帰を使って繰り返しを行っています。(今から考えるとわざわざ再帰を使わなくていいような気がします)

これでsimplenumber->romanの説明が終わりました。あとは最初に述べたとおり与えられた整数をinteger-refで分解してそれぞれにsimplenumber->romanを適用し、その結果をstring-appendで畳み込むだけです。

gosh> (number->roman 2014)
"MMXIV"
gosh> (number->roman 1234)
"MCCXXXIV"
gosh> (number->roman 23)
"XXIII"
gosh> (number->roman 159)
"CLIX"

完璧ですね!(多分)


これはまだ簡単な例ですが、いつもはもう少し複雑な関数/マクロについて発表・議論を行います。
興味がある方はぜひKMCに入会することをご一考ください。

KMCには入部制限はありません。年齢や学歴、人種、宗教、信条、性別、社会的身分、門地、国籍、経験などは不問です。また活動に関する制約もありません。IRCのチャット越しに会話に参加することだけでも大丈夫です。詳細は下記Webページを御覧ください。

(KMC活動ブログ・「今年もやります!KMCアドベントカレンダー!!」より)


ということでKMCアドベントカレンダー6日目「関数品評会」でした。
明日のKMCアドベントカレンダーはyui9(id:yu_i9)さんの「Haskell正規表現」です。