@chezou さんの ズンドコキヨシ.jl を見て、私もズンドコキヨシを Julia で書いてみたくなった。
ただのn番煎じじゃつまらないので、できる限りトリッキーに書いてみた。
※【2016/03/17 20:20】 厳密バージョンについて追記しました。
Iteration による実装
# zundoko_iter.jl
immutable ZunDoko end
Base.start(::ZunDoko) = 0
Base.done(::ZunDoko, i::Int) = i == 6
function Base.next(::ZunDoko, i::Int)
if i == 5
("キ・ヨ・シ!", 6)
elseif i == 4
rand([("ズン", 4), ("ドコ", 5)])
else
rand([("ズン", i + 1), ("ドコ", 0)])
end
end
function runzundoko()
for cw in ZunDoko()
print(cw)
end
println()
end
runzundoko()
# => ズンズンズンドコズンズンズンドコドコドコドコズンズンズンズンズンドコキ・ヨ・シ!
Julia の Iteration の実装を利用。
(Iteration については、私の過去記事等参照)
列挙そのものはこの Iteration の仕組みに依っているので、出力するのではなくリストで取得したければ、例えば
collect(UTF8String, ZunDoko())
のようにすれば ["ズン", "ズン", "ドコ", "ズン", "ドコ", "ドコ", "ズン", "ドコ", "ドコ", "ズン", "ズン", "ズン", "ズン", "ドコ", "キ・ヨ・シ!"]
というリスト(配列)が得られます1。
Iteration + Value Type による実装
# zundoko_iter_val.jl
immutable ZunDoko end
Base.start(::ZunDoko) = Val{0}
Base.done(::ZunDoko, ::Type{Val{6}}) = true
Base.done{N}(::ZunDoko, ::Type{Val{N}}) = false
Base.next(::ZunDoko, ::Type{Val{5}}) = ("キ・ヨ・シ!", Val{6})
Base.next(::ZunDoko, ::Type{Val{4}}) = rand([("ズン", Val{4}), ("ドコ", Val{5})])
Base.next{N}(::ZunDoko, ::Type{Val{N}}) = rand([("ズン", Val{N+1}), ("ドコ", Val{0})])
function runzundoko()
for cw in ZunDoko()
print(cw)
end
println()
end
runzundoko()
# => ドコドコズンズンズンドコズンズンドコズンドコドコズンドコズンズンズンズンドコキ・ヨ・シ!
先の例 をさらに発展させて、Value Typeを利用して if〜
もなくしてみました。
Julia は関数引数の値によるパターンマッチングはサポートしていませんが、Val{T}
の型パラメータT
として渡せるものに限定すれば、このようにパターンマッチング的な書き方もできます。
ただ、Base.done(::ZunDoko, ::Type{Val{6}})
関数 とか Base.next(::ZunDoko, ::Type{Val{5}})
関数 なんかは1回しか実行されないのでJITコンパイルによる最適化が有効に働かなくて、パフォーマンス的には今回はあまり効果がないのが残念。
まとめに変えて
もっと Julia でズンドコキヨシが見てみたい。
できれば、追加パッケージとか使っても良いから、もっとすっきりした実装を。
【2016/03/17 20:20 追記】厳密バージョン
元ネタ:
Javaの講義、試験が「自作関数を作り記述しなさい」って問題だったから
— てくも (@kumiromilk) March 9, 2016
「ズン」「ドコ」のいずれかをランダムで出力し続けて「ズン」「ズン」「ズン」「ズン」「ドコ」の配列が出たら「キ・ヨ・シ!」って出力した後終了って関数作ったら満点で単位貰ってた
元の文章が「『ズン』『ズン』『ズン』『ズン』『ドコ』が出たら『キ・ヨ・シ!』」だったので、とにかく4つ(以上)「ズン」が来てその後に「ドコ」が来たら「キ・ヨ・シ!」で終わり、で良いと思ってました。
つまり先述の私のコードでは、ズン4ドコ1だけでなくズン5ドコ1でもズン100ドコ1でも「キ・ヨ・シ!」発動するようになっています。
でも ズンドコで学ぶ要件定義(未完) を拝見して。
C:1. 設計ではズン4ドコ1でキヨシーだが、運用でズン5ドコ1でキヨシーとなっている。どちらを正とするべきか
C:2. ズン4ドコ1を正としたとき、ズン5ドコ1はキヨシー発動条件として認めるか
確かに。厳密に4つ(だけ)「ズン」が来てその後に「ドコ」が来たら「キ・ヨ・シ!」というのが元々の要件だった可能性は大いにありますね。
ということでそんな「厳密バージョン」も書いてみました(分かりやすいので Iteration による実装の方だけ):
# zundoko_iter_strict.jl
immutable ZunDoko end
Base.start(::ZunDoko) = 0
Base.done(::ZunDoko, i::Int) = i == 6
function Base.next(::ZunDoko, i::Int)
if i == 5
("キ・ヨ・シ!", 6)
elseif i == 4
rand([("ズン", -1), ("ドコ", 5)])
elseif i == -1
rand([("ズン", -1), ("ドコ", 0)])
else
rand([("ズン", i + 1), ("ドコ", 0)])
end
end
function runzundoko()
for cw in ZunDoko()
print(cw)
end
println()
end
runzundoko()
# => ドコズンズンズンズンズンズンドコズンズンズンズンドコキ・ヨ・シ!
はい。簡単ですね。1行修正2行追加しただけです。
私のこの実装方法は、「それまでの『ズン』『ドコ』がどういうパターンで並んでいるか」を判定することはしないで、「次に出力する『ズン』『ドコ』が求められているシーケンスの何番目か」を整数値という「ステータス」で持つようにしたもの。
ついでに初期ステータスを 0 としており、3回以下(および厳密バージョンでは5回以上も)「ズン」が続いた後の「ドコ」もステータス 0 にしているので、「開始がいきなり『ズンズンズンズンドコ』、または『ドコズンズンズンズンドコ』」という場合分けが不要でどちらでも正しく判定出来ます。
もちろん Iteration + Value Type による実装 も、Elixir に翻訳した ズンドコキヨシ.ex by ストリーム without Stream
も全く同様の対応で「厳密バージョン」化できますね。
-
リスト内包表記を用いて
[w for w=ZunDoko()]
のように書くことはできません。ZunDoko
型にlength()
関数へのメソッドが定義されていない(というか結果は不定なので定義出来ない)ためです。 ↩