LoginSignup
3
4

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-02-20

嘘つき族と正直族の問題
何番煎じか知れませんが、おもしろそうなのでやってみました。

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)
# 結果:
((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 通りまで減少します。

>>> 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通りしかないんだったら、タプルじゃなくて単純な論理値で表現できる。
表示が重要なら、そっちでちょっと工夫すればよい。

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 の内容を書き換えても動きそうなものはこれです。

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 でやった 「人への言及 を カードの手に関する言い分に変換」するのは引き続き有効です。

3
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
4