Sleeping barber problem
id:sumimさんが投稿された、
居眠り床屋問題 http://ja.doukaku.org/285/
丁度F#の並列計算を(ちまちまですが、)勉強していたところだったので、標準クラスのMailboxProcessorを使って解いてみました。ここでこれを使うべきじゃないかもしれませんが、まあ男とはそういう生き物です。
MailboxProcessorとは、Erlangのプロセス間通信(メッセージ・パッシング)みたいなもので、スレッド間でメッセージをやり取りするための仕組み(を提供してくれるクラス)です。ライブラリレベルでの提供なので、Erlangほどしっかりしたものではないようです。(Erlangは良く分かってません)
名前から分かるように、相手のメールボックスにPostしておいて、それを好きなタイミングでReceiveするというのが基本的な流れです。ベイジアンフィルタは実装されていません。
気が向けば(僕は相当気まぐれですが)MailboxProcessorの簡単な解説も書くかもしれません*1。しかし今日はとりあえず回答を丸投げしておきます。
無理矢理な部分が多くて読みづらいと思うので、処理の流れを簡単に書いておきます。
- 客を一度に生成して、
- メインのスレッドで一人ずつ訪問(問い合わせメールを送信)させます。
- サロンでは客(メール)が来ると席を確認し、カット可能かどうか返信(true|false)します。受付のお姉さんの仕事です。ちなみに彼氏持ちなので連絡先を聞いても無駄です。
- 店内に案内されました。なかなか洒落た店内です。BGMが少しうるさいのが気になります。受付のお姉さんが美容師に客の身柄を受け渡します。(なんと大事なお客様はメールに添付されるのです)
- この時点で美容師が寝ていれば起きます。当たり障りの無いつまらないトークをしながら、美容師はハサミを動かします。
- カットが終了すると、「この人終わったよ」と受付のお姉さんに知らせます(送り返します)。なんとまたしてもお客様はメールに貼り付けられます。
- その通知を見て、お姉さんは待機中の次の客を美容師に渡します。客がいなければ美容師は居眠りを始めます。※日中に慢性的に強い眠気を感じる方は、睡眠時無呼吸症候群などの疑いがあります。
- この処理を、営業終了メールを受け取るまで繰り返します
というストーリーを描いたのが以下のプログラムです。
せっかくの関数型言語ということで、意地になって破壊的代入を排除しました。(一部残ってますが・・・)
open System open System.Threading type Visitor (id:int, requiredTime:int) = member self.ID = id member self.RequiredTime = requiredTime static member GenVisitors () = let rnd = new Random() [ for id in 1..16 -> new Visitor(id, rnd.Next(100, 400)) ] type Message = /// 営業開始 | Open /// ご来店〜 | Request of AsyncReplyChannel<bool> * Visitor /// カット終了! | Finished of Visitor /// 閉店でーす | Close type StylistAction = | Cut of Visitor | Sleep | End type Stylist = MailboxProcessor<StylistAction> type Salon () = /// 空きが無ければNone, 空いてれば新しいseats let SetSeats (seats : Visitor list) newVisitor = if seats.Length >= 3 then None else Some (newVisitor :: seats) let rec Last = function | [] -> Sleep | x :: [] -> Cut x | _ :: cdr -> Last cdr // 相互に参照させるための929の39(苦肉の策) [<DefaultValue>] val mutable salon : MailboxProcessor<Message> [<DefaultValue>] val mutable stylist : Stylist member self.CreateSalon (stylist : Stylist) = let Post (newSeats : Visitor list) = stylist.Post <| Last newSeats ; newSeats // 処理の心臓部分 let Switch seats = function | Open -> Post seats | Close -> [(*´ω`*)] | Finished visitor -> Post <| List.filter (fun v -> v <> visitor) seats | Request (mail, visitor) -> match (SetSeats seats visitor) with | None -> mail.Reply false ; seats | Some newSeats -> mail.Reply true ; newSeats let salon' = new MailboxProcessor<Message>(fun mailbox -> let rec DoBusiness (seats : Visitor list) isSleeping = async { // 起きなきゃいけない? if isSleeping && seats.Length = 1 then stylist.Post <| Last seats let! msg = mailbox.Receive () if msg = Close then return () return! DoBusiness (Switch seats msg) (seats = []) } DoBusiness [] true ) self.salon <- salon' ; salon' member self.CreateStylist () = let Cut (v : Visitor) = printfn "カット開始! %d" v.ID Thread.Sleep( v.RequiredTime ) self.salon.Post (Finished v) printfn "カット終了! %d" v.ID let stylist = new Stylist(fun jobbox -> let rec MakeHair isSleeping = async { let! job = jobbox.Receive () match job with | End -> return () | Sleep -> printfn "Zzz...(-_-)" return! MakeHair true | Cut visitor -> if isSleeping then printfn "Σ(゚_゚)" Cut visitor return! MakeHair false } MakeHair false ) self.stylist <- stylist ; stylist member self.Start () = let visitors = Visitor.GenVisitors () let span = let r = Random() in r.Next(0, 200) use stylist = self.CreateStylist () use salon = self.CreateSalon stylist let count = ref 0 stylist.Start () ; salon.Start () salon.Post Open for v in visitors do if v.ID = 9 then Thread.Sleep 1200 else Thread.Sleep span printfn "来店 %d" v.ID let ok = salon.PostAndReply (fun (x:AsyncReplyChannel<bool>) -> Request (x,v)) if ok then incr count else printfn "満席で立ち去る %d" v.ID done // 仕事終わりを待ってから終了メールを出す Thread.Sleep 1200 stylist.Post End salon.Post Close printfn "※ %d人のうち%d人をカット" visitors.Length (!count) let Test_Salon () = (Salon ()).Start () |> ignore
出力例:
Zzz...(-_-)
来店 1
Σ(゚_゚)
カット開始! 1
来店 2
来店 3
カット終了! 1
カット開始! 2
来店 4
来店 5
満席で立ち去る 5
カット終了! 2
カット開始! 3
来店 6
カット終了! 3
カット開始! 4
来店 7
来店 8
満席で立ち去る 8
カット終了! 4
カット開始! 6
カット終了! 6
カット開始! 7
カット終了! 7
Zzz...(-_-)
来店 9
Σ(゚_゚)
カット開始! 9
来店 10
カット終了! 9
カット開始! 10
来店 11
カット終了! 10
カット開始! 11
来店 12
来店 13
カット終了! 11
カット開始! 12
来店 14
来店 15
満席で立ち去る 15
カット終了! 12
カット開始! 13
来店 16
カット終了! 13
カット開始! 14
カット終了! 14
カット開始! 16
カット終了! 16
Zzz...(-_-)
※ 16人のうち13人をカット
ご意見・ご感想・ご指摘などありましたらよろしくお願いします!
*1:日本語の情報が皆無でちょっと苦労したので