Alloyでパズル (3) シマウマのパズル

きのう Qiita でパズルを検索して出てきた [[Prologで論理パズル] グリッドパズル；シマウマのパズル](https://qiita.com/Soichir0/items/daa0b9df8a2500ae0a04) をやってみた。
やったと言っても、解き方を考えないので問題文を言い換えただけに近い。

# 問題

そのまま引用、長い。
> 家が5軒一列に並んで建っている。
5軒の家はそれぞれ違う色に塗られていて、住んでいる人はそれぞれ生まれた国、飼っているペット、飲み物、履物の種類が違う。
>
- スコットランド人は赤い家に住んでいる。
- ギリシャ人は犬を飼っている。
- コーヒーを飲むのは緑色の家の人。
- ボリビア人はお茶を飲む。
- 緑色の家はクリーム色の家のすぐ右隣りにある。
- 革靴を履いている人はカタツムリを飼っている。
- 厚底靴を履くのは黄色の家の人。
- 牛乳を飲むのは真ん中の家の人。
- デンマーク人は左端の家に住んでいる。
- 革サンダルを履いている人は、キツネを飼っている人の隣の家に住んでいる。
- 厚底靴を履いている人はの家は、ウマを飼っている家の隣。
- スリッパを履いている人はオレンジジュースを飲む。
- 日本人はビーチサンダルを履いている。
- デンマーク人は青い家の隣に住んでいる。
>
さて、水を飲むのは誰？また、シマウマを飼っているのは誰？

# リスト

Alloy の場合は、問題をなるべく素直に記述するだけで終わりなのであまり難しいところはない。
日本語で書かれた問題文をどのくらい違和感なく記述できるかに注目してほしいくらい。
「家が５軒一列に並んで建っている」の表現は少々特殊だが、読むぶんには問題ないだろう。
（House1の右はHouse2、House2の右はHouse3、.. と１文で書いている）
enum の値は参照先のものをそのまま拝借した。

```alloy:puzzle3.als
// シマウマのパズル
// https://qiita.com/Soichir0/items/daa0b9df8a2500ae0a04

// 家が5軒一列に並んで建っている
// 5軒の家はそれぞれ違う色に塗られていて、住んでいる人はそれぞれ生まれた国、
// 飼っているペット、飲み物、履物の種類が違う
enum Color {Blue, Green, Red, Yellow, Cream}
enum Nationality {Denmark, Japanese, Bolivian, Scottish, Greece}
enum Pet {Snails, Dog, Horse, Fox, Zebra}
enum Drink {Tea, Orange, Milk, Coffee, Water}
enum Footwear {Leather, Slipper, LeatherS, BeachS, Platform}

abstract sig House {
    color: disj Color,                      // それぞれの色は違う
    left,right: lone House                  // 左右にはたかだか１軒の家がある
}
one sig House1,House2,House3,House4,House5 extends House {}
fact 家は一列に並ぶ {
    right = House1->House2 + House2->House3 + House3->House4 + House4->House5
    left = ~right       // 左は右の反対
}

sig Person {
    house: disj House,                      // それぞれ別の家に住んでいる
    nationality: disj Nationality,          // それぞれの国籍は違う
    pet: disj Pet,                          // それぞれのペットは違う
    drink: disj Drink,                      // それぞれの飲み物は違う
    footwear: disj Footwear                 // それぞれの履物は違う
}

fact {
    nationality.Scottish.house.color = Red  // スコットランド人は赤い家に住んでいる
    nationality.Greece.pet = Dog            // ギリシャ人は犬を飼っている
    drink.Coffee.house.color = Green        // コーヒーを飲むのは緑色の家の人
    nationality.Bolivian.drink = Tea        // ボリビア人はお茶を飲む
    color.Green = color.Cream.right         // 緑色の家はクリーム色の家のすぐ右隣りにある
    footwear.Leather.pet = Snails           // 革靴を履いている人はカタツムリを飼っている
    footwear.Platform.house.color = Yellow  // 厚底靴を履くのは黄色の家の人
    drink.Milk.house = House3               // 牛乳を飲むのは真ん中の家の人
    nationality.Denmark.house.left = none   // デンマーク人は左端の家に住んでいる
    // 革サンダルを履いている人は、キツネを飼っている人の隣の家に住んでいる
    footwear.LeatherS.house in pet.Fox.house.(left+right)
    // 厚底靴を履いている人はの家は、ウマを飼っている家の隣
    footwear.Platform.house in pet.Horse.house.(left+right)
    footwear.Slipper.drink = Orange         // スリッパを履いている人はオレンジジュースを飲む
    nationality.Japanese.footwear = BeachS  // 日本人はビーチサンダルを履いている
    // デンマーク人は青い家の隣に住んでいる
    nationality.Denmark.house in color.Blue.(left+right)
}
// 問題：水を飲むのは誰？ シマウマを飼っているのは誰？

run {} for exactly 5 Person
```
「〜は」の位置に`=`を移動して`nationality.Scottish = house.color.Red`としても大丈夫。
見た通り、家の色を比較するか人を比較するかの違いになる。
「真ん中の家」は「３軒左にも３軒右にも家がない」とも書けるが、長いので単に House3 とした。

# 結果

水を飲むのはデンマーク人、シマウマを飼っているのは日本人だった。
Evaluator で`drink.Water`,`pet.Zebra`などと打ってそれが何を示すか確認することもできる。
特定の`Person`から`pet`や`drink`を引くだけでなく、逆に`pet.Dog`や`drink.Water`から`Person`を引けるのが面白いところ。
　　<img width="867" alt="zebra.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20005/a1f85ac3-f654-9e27-c3ed-faae688655a0.png">

# 解説：わかった気になるドット結合
どうやって`drink.Water.nationality`は`Denmark`にたどり着くのか、Evaluator で出た各段階を切り貼りで解説を。
まず`drink`は、`Person->Drink`の一覧を意味する（`sig Person {drink: Drink}`より）
ドット結合では左辺と右辺の共通行をつなぎ、当てた列自体は消す。
そのため`drink.Water`の時点で左辺`Water`の行から最右列を消した`Person$0`だけが残る。
そこへ右から`nationality`を当てると、右辺`Person$0`行の最左列が消えた`Denmark`が残る。
　　<img width="529" alt="dotOpe.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20005/48b4ce8e-d7a3-76f8-b052-25fd66eaeafa.png">
ついでに、２項関係の場合は`~`で列反転できるので、`(~nationality).drink`で国籍別飲み物一覧が得られる。
　　<img width="167" alt="dotOpe2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20005/e568e317-9173-7272-0165-e724e11c4276.png">
なお、ドットに接する左右の列が異なる型だった場合、共通する行がないため単に空集合となる。
だいたいこのくらいの理解でよろしいかと。
結果、逆引きだからと言ってコストが高いということはない。

# 参考

- [[Prologで論理パズル] グリッドパズル；シマウマのパズル](https://qiita.com/Soichir0/items/daa0b9df8a2500ae0a04)