Create a gist now

Instantly share code, notes, and snippets.

@torus /README.md
Last active Apr 10, 2016

What would you like to do?
ズンドコキヨシ

ズンドコキヨシ

ズンドコキヨシまとめ - Qiita

ぼくもつくってみた。

Lua のコルーチンを使った実装

ズンかドコをひたすら生成するジェネレータと、その出力を受け取って条件が揃ったらキヨシを返すコンシューマをコルーチンとして実装しました。

Lua のコルーチンはまず普通の関数としてコルーチンの中身を定義します。ジェネレータはこんな感じです。

function zundoko()
   local choice = {"ズン", "ドコ"}

   while true do
      local index = math.random(1, 2)
      coroutine.yield(choice[index])
   end
end

coroutine.yield() を使って呼び出し元に値を返します。

コンシューマはこんな感じです。

function kiyoshi(input)
   local zun = 0
   while true do
      if input == "ズン" then
         zun = zun + 1
      else
         if input == "ドコ" and zun >= 4 then
            return "キ・ヨ・シ!"
         end
         zun = 0
      end
      input = coroutine.yield()
   end
end

zun という変数でズンが何回連続して渡されたかを数えています。 4 回以上ズンが連続した状態でドコを受け取ると、キヨシを return を使って返して終了します。 それ以外の場合は何もせずに coroutine.yield() で呼び出し元に処理を戻します。

上で作った関数からコルーチンを生成します。

local zundoko_co = coroutine.create(zundoko)
local kiyoshi_co = coroutine.create(kiyoshi)

メイン部分では zundoko_co から値を 1 個受け取って kiyoshi_co に渡します。 kiyoshi_co が何か返したらそれを出力します。 また、コルーチンは return で戻ると死亡状態になるので、ここでループが終了するようにしました。

while coroutine.status(kiyoshi_co) ~= "dead" do
   local state, input = coroutine.resume(zundoko_co)
   print(input)
   local state, output = coroutine.resume(kiyoshi_co, input)
   if output then
      print(output)
   end
end

coroutine.yield() はループの内側からコルーチンの外側に一気に脱出して、後で再びループの内部に戻ってこれるので便利です。

実行すると以下のように出力されます。

localhost:zundoko toru$ lua zundoko.lua
ドコ
ズン
ドコ
ドコ
ドコ
ズン
ズン
ドコ
ズン
ドコ
ズン
ドコ
ズン
ドコ
ドコ
ドコ
ドコ
ドコ
ズン
ドコ
ズン
ズン
ズン
ドコ
ズン
ズン
ズン
ズン
ドコ
キ・ヨ・シ!
localhost:zundoko toru$ 

Scheme の継続を使った実装

Lua で作ったコルーチンとだいたい同じ動きをするように継続を使って実装してみました。 処理系には Gauche を使っています。

まずズンかドコをひたすら出力するジェネレータ部分はこんな感じです。

(define (zundoko cont)
  (let loop ((next cont))
    (let1 next-cont (call/cc (lambda (inner-cont)
                               (next (if (< (random-real) 0.5) 'ズン 'ドコ) inner-cont)))
      (loop next-cont))))

値を返すための継続手続きを引数にとります。 この手続きの中では名前付き let をつかって無限ループを回しています。 そして、値を生成し引数で受け取った継続手続きを使って値を返しますが、このとき call/cc を使って自分自身の継続手続きも作って一緒に呼び出し元に渡しています。

次にズンかドコを受け取って内部状態を更新するコンシューマ部分です。

(define (kiyoshi input cont exit)
  (let loop ((zun 0)
             (next cont))
    (let-values (((input next-cont exit)
                  (call/cc (lambda (inner-cont)
                             (next inner-cont)))))
      (if (eq? input 'ズン)
          (loop (+ 1 zun) next-cont)
          (if (>= zun 4)
              (exit 'キ・ヨ・シ!)
              (loop 0 next-cont))))))

ここでも、継続手続きを引数にとりますが、3 番目の引数としてループを終了するための大域脱出用の継続手続きも受け取っています。

input として 'ズン を受け取った回数をカウントしています。 'ドコ を受け取ると 'ズン の回数は 0 にリセットします。 ここでも、あとからループの途中に戻ってこれるように call/cc で継続手続きを作って呼び出し元に返しています。

そして 'ズン を 4 回以上連続して受け取った状態で 'ドコ を受け取ると、大域脱出用の exit という手続きを呼び出します。 Lua 版と違うのは、このように値を返す先が複数あるところです。

最後にメイン部分です。ここでも名前付き let で無限ループを実行しています。

(print (call/cc
        (lambda (exit)
          (let loop ((zun zundoko)
                     (kiyo kiyoshi))
            (let-values (((input new-zun)
                          (call/cc (^[cont] (zun cont)))))
              (print input)
              (let1 new-kiyo (call/cc (^[cont] (kiyo input cont exit)))
                (loop new-zun new-kiyo)))))))

call/cc が 3 個もありますが、1 番目は「キ・ヨ・シ!」を出力してプログラムを終了するための大域脱出用の継続手続きを作るためのもので、残りの 2 個はそれぞれジェネレータとコンシューマに渡すためのものです。

function zundoko()
local choice = {"ズン", "ドコ"}
while true do
local index = math.random(1, 2)
coroutine.yield(choice[index])
end
end
function kiyoshi(input)
local zun = 0
while true do
if input == "ズン" then
zun = zun + 1
else
if input == "ドコ" and zun >= 4 then
return "キ・ヨ・シ!"
end
zun = 0
end
input = coroutine.yield()
end
end
local zundoko_co = coroutine.create(zundoko)
local kiyoshi_co = coroutine.create(kiyoshi)
while coroutine.status(kiyoshi_co) ~= "dead" do
local state, input = coroutine.resume(zundoko_co)
print(input)
local state, output = coroutine.resume(kiyoshi_co, input)
if output then
print(output)
end
end
(use srfi-11)
(use srfi-27)
(define (zundoko cont)
(let loop ((next cont))
(let1 next-cont (call/cc (lambda (inner-cont)
(next (if (< (random-real) 0.5) 'ズン 'ドコ) inner-cont)))
(loop next-cont))))
(define (kiyoshi input cont exit)
(let loop ((zun 0)
(next cont))
(let-values (((input next-cont exit)
(call/cc (lambda (inner-cont)
(next inner-cont)))))
(if (eq? input 'ズン)
(loop (+ 1 zun) next-cont)
(if (>= zun 4)
(exit 'キ・ヨ・シ!)
(loop 0 next-cont))))))
(print (call/cc
(lambda (exit)
(let loop ((zun zundoko)
(kiyo kiyoshi))
(let-values (((input new-zun)
(call/cc (^[cont] (zun cont)))))
(print input)
(let1 new-kiyo (call/cc (^[cont] (kiyo input cont exit)))
(loop new-zun new-kiyo)))))))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment