抽象によるソフトウェア設計 付録A 練習問題 その15
付録A 練習問題続きます。A.3 の古典的パズル続きです。
- A.3.4 Halmos の握手問題
sig Person { shakehands: set Person, partner: one Person } one sig Alice in Person {} { partner = Bob } one sig Bob in Person {} { partner = Alice } fact { // shakehands は対称的 ~shakehands in shakehands // partner も対称的 ~partner = partner // パートナーは自分自身ではない all p: Person | not p = p.partner // 自分自身とは握手しない no iden & shakehands // 自分のパートナーとも握手しない all p: Person | no p.partner & p.shakehands } fact { // Alice 以外の全員が握手した回数が異なる all disj p1, p2: (Person - Alice) | #p1.shakehands != #p2.shakehands } run {} for exactly 10 Person assert BobShakehandsWithFourPerson { #Bob.shakehands = 4 } check BobShakehandsWithFourPerson for exactly 10 Person
-
- 解析器で例をいくつか作ってみるとどうやら Bob の握手した回数は常に 4 らしい。そこで assert を追加して check コマンドで検証したらやはり10人の参加者の場合は常に 4になる
- (b) Bob の握手する回数が異なる別解があるかもしれないのでモデルを拡張せよ、という問題なのですが、どこをいじっていいのかわからないのでパス。回数変えられるのかな?
- A.3.4 ヤギ、キャベツ、狼
- 目を離すとヤギがキャベツを、狼がヤギを食べてしまうという条件のなかで1つの舟でどうやって川を渡るかというよくあるパズル
open util/ordering [Time] sig Time { dir: Direction } enum Direction { Forward, Backward } abstract sig Place { exist: Property set -> Time } one sig NearSide extends Place {} one sig Boat extends Place {} {} one sig FarSide extends Place {} abstract sig Property { eat: set Property } one sig Cabbage extends Property {} { eat = none } one sig Goat extends Property {} { eat = Cabbage } one sig Wolf extends Property {} { eat = Goat } fact { // 複数の場所に同時に存在することはできない all p: Property | all t: Time | #exist.t.p = 1 // Boat には1つまでしか積めない all t: Time | #Boat.exist.t < 2 } pred init (t: Time) { // 最初はボートが NearSide 側にある t.dir = Forward // こちら側にキャベツ、山羊、狼がいて他は空 NearSide.exist.t = Cabbage + Goat + Wolf Boat.exist.t = none FarSide.exist.t = none } // 食ってしまうペアがいる場所はない pred AvoidTragedy (t: Time) { all p: Place | no p1, p2: Property | p1 in exist[p].t and p2 in exist[p].t and p2 in p1.eat } // 荷の積み降ろし。ボートに乗せた荷は常に降ろす // 乗せたまま引き換えすと同じ状態の繰り返しで意味がないので除外 pred Charge (t, t': Time, side: Place) { lone p: side.exist.t | side.exist.t' = side.exist.t - p + Boat.exist.t and Boat.exist.t' = p } // 反対の場所の状態は変化しないことを制約にする pred NotChange (t, t': Time, side: Place) { side.exist.t' = side.exist.t } pred Transit (t, t': Time) { t.dir = Forward implies Charge [t, t', NearSide] and NotChange [t, t', FarSide] and t'.dir = Backward else Charge [t, t', FarSide] and NotChange [t, t', NearSide] and t'.dir = Forward } pred traces { init [first] all t: (Time - last) | let t' = t.next | Transit [t, t'] and AvoidTragedy [t'] } run { traces // 最後はボートが向こう岸に着いてて last.dir = Backward // 最後の荷をボートに乗せている状態 #FarSide.exist.last = 2 and #Boat.exist.last = 1 } for 8 Time
-
- util/ordering を使って順序付きの Time シグネチャを導入して時間の概念を入れる方法
- 両岸(NearSide, FarSide)から荷の積み降ろしをして引き換えすごとを1つのイベントとしている
- 最後のイベントは全部の荷が渡ったところではなくて、最後の荷をボートに乗せて向こう岸に着いた状態とするようにしている
A.3 のパズルはあと1問です。これがモデル化が難しそう。