Python:嘘つき族と正直族をやってみた

[嘘つき族と正直族の問題](https://qiita.com/redsquid/items/ae493065a4193516f2c6)
何番煎じか知れませんが、おもしろそうなのでやってみました。

```py:
import itertools

# プレーヤーとカードデッキと言い分から、矛盾のない答を返す
consistents = (
  lambda players: lambda card_deck: lambda statements:
  (
    (hands, is_each_honest) 
    for hands in handss(card_deck) 
    for is_each_honest in is_each_honests
    if  statements(hands) == is_each_honest 
  )
)
# それぞれのカードの手のタプルを返すジェネレータ
handss = lambda card_deck: itertools.permutations(card_deck)

# それぞれのプレーヤーが正直かどうか のタプルのタプル (決め打ち2)
is_each_honests = (
  (True, False, False, True)
  ,(False, True, True, False)
)

# データ
players = (0, 1, 2, 3)
card_deck = (1, 2, 3, 4)

statements = lambda hands: (
  hands[0] % 2 == 0
  , hands[1] in (3, 4)
  , hands[1] in (3, 4)    # (決め打ち1)
  , hands[3] == 1
)

# 関数適用と表示
for e in consistents(players)(card_deck)(statements):
  print(e)
```

```py:
# 結果：
((1, 3, 2, 4), (False, True, True, False))
((1, 3, 4, 2), (False, True, True, False))
((1, 4, 2, 3), (False, True, True, False))
((1, 4, 3, 2), (False, True, True, False))
((3, 4, 1, 2), (False, True, True, False))
((4, 2, 3, 1), (True, False, False, True))
```

決め打ちしてるところがあります。

### 決め打ち1：「Bは正直者」=>「Bの言ってることは正しい」=>Bの言ってること=>「Bのカードは3か4」  に変換
カードの手が決まればそれぞれの言い分の真偽が決まるので、問題がシンプルになります。

### 決め打ち2：嘘つき/正直は二人づつ=>BとCは同じ言い分=>BとCは同族=>AとDは逆の方の同族
制約がなければ全部で 6 通りですが、BとCが同族となると、バリエーションは 2 通りまで減少します。

```py:
>>> players = 0, 1, 2, 3
# 制約なし
>>> tuple( tuple( not e in liars for e in players ) for liars in itertools.combinations(players, 2))
((False, False, True, True), (False, True, False, True), (False, True, True, False), (True, False, False, True), (True, False, True, False), (True, True, False, False))    # 6 通り
# 制約あり 嘘つきは ADか、でなければBC
>>> tuple( tuple( not e in liars for e in players ) for liars in itertools.combinations(players, 2) if liars in ((0,3),(1,2)))
((False, True, True, False), (True, False, False, True))    # 2 通り
```
計算してもいいけど、どうせふたつしかないんで、そこは直書きでいいかな、と。

以上のことをふまえて、
## もちょっと短く
してみます。
###決め打ち1=>BとCをまとめる
BとCは 言い分 も 正直かどうか も一緒なので。
###決め打ち2=>True/False にする
2通りしかないんだったら、タプルじゃなくて単純な論理値で表現できる。
表示が重要なら、そっちでちょっと工夫すればよい。

```py:
import itertools

# プレーヤーとカードデッキと言い分から、矛盾のない答を返す
consistents = (
  lambda players: lambda card_deck: lambda statements:
  (
    (hands, is_AD_honest) 
    for hands in itertools.permutations(card_deck)
    for is_AD_honest in (True, False)    # (決め打ち2)
    if  statements(hands) == (is_AD_honest, not is_AD_honest, is_AD_honest) # (決め打ち1) 
  )
)

# データ
players = (0, 1, 2, 3)
card_deck = (1, 2, 3, 4)

statements = lambda hands: (
  hands[0] % 2 == 0
  , hands[1] in (3, 4)    # (決め打ち1)
  , hands[3] == 1
)

# 関数適用と表示
for hands, is_AD_honest in consistents(players)(card_deck)(statements):
  print(
    (hands, (is_AD_honest, not is_AD_honest, not is_AD_honest, is_AD_honest)) # (決め打ち2)
  )
```
わかりやすいか はおいといて、ちょっと短くはなりました。

でも、そもそも...
# 決め打ちしすぎじゃない?

それぞれの言い分が違ったら(たとえば、Cが「Bは嘘つき」と言ったら)、決め打ちした部分は役に立たなくなります。いちからやりなおしです。

それぞれの言い分 `statements` の内容を書き換えても動きそうなものはこれです。

```py:
import itertools

# プレーヤーとカードデッキと言い分から、矛盾のない答を返す
consistents = (
  lambda players: lambda card_deck: lambda statements:
  (
    (hands, is_each_honest) 
    for hands in handss(card_deck) 
    for is_each_honest in is_each_honests(players)
    if  statements(hands) == is_each_honest 
  )
)
# それぞれのカードの手のタプルを返すジェネレータ
handss = lambda card_deck: itertools.permutations(card_deck)

# それぞれのプレーヤーが正直かどうか のタプルを返すジェネレータ 
is_each_honests = lambda players:( 
  tuple( e in honests for e in players ) 
  for honests in itertools.combinations(players, 2)
)

# データ
players = (0, 1, 2, 3)
card_deck = (1, 2, 3, 4)

statements = lambda hands: (
  hands[0] % 2 == 0
  , hands[1] in (3, 4)
  , hands[1] in (3, 4)    
  , hands[3] == 1
)

# 関数適用と表示
for e in consistents(players)(card_deck)(statements):
  print(e)
```
 ただし、決め打ち1 でやった 「人への言及 を カードの手に関する言い分に変換」するのは引き続き有効です。