Chevaliers et Knaves

12

C'est du .

Dans ce défi, nous allons écrire des programmes / fonctions qui résolvent les puzzles " Knights and Knaves ".

Contexte

Vous vous retrouvez sur une île ... etc ... chaque personne sur l'île sauf vous est soit un chevalier soit un valet .

Les chevaliers ne peuvent que faire de véritables déclarations.

Les knaves ne peuvent faire que de fausses déclarations.

Je ne veux pas définir rigoureusement «déclaration», mais nous dirons qu'une déclaration est tout ce qui est «vrai» ou «faux». Notez que cela exclut les phrases paradoxales.

Aux fins de ce défi, vous rencontrerez des groupes d'insulaires; ils vous feront des déclarations.

Votre tâche consiste à déterminer qui est un chevalier et qui est un knave.

Contribution:

Vous recevrez (dans tout format raisonnable) les informations suivantes:

  • Une liste des personnes présentes. Ils seront nommés avec des caractères alphabétiques majuscules "AZ". La limite du nombre de personnes imposée par cela ne sera pas dépassée.

  • Les déclarations que chacun fait. Voir ci-dessous pour des détails importants à ce sujet.

Production

Vous afficherez ensuite (dans n'importe quel format raisonnable) ce que chaque personne est. Par exemple, s'il y avait des joueurs A B C Det qu'il As'agit d'un chevalier, mais que les autres sont des chevaliers, vous pourriez produire

A: 1
B: 0
C: 0
D: 0

Détails importants:

  • Les caractères alphabétiques majuscules AZ désignent les insulaires.

  • Les caractères 0(zéro) et 1(un) font respectivement référence à un "Knave" et à un "Knight". (Vous pouvez utiliser deux autres caractères non AZ, tant que vous spécifiez)

  • Chaque insulaire présent peut faire un nombre naturel de déclarations, ou peut choisir de ne rien dire.

  • Les opérateurs logiques normaux peuvent être utilisés dans des instructions ( IS *, AND, OR, NOT ). En plus de cela, les lois et conditions de De Morgan peuvent être utilisées. Voici des exemples de la façon dont ils pourraient être présentés dans un puzzle parlé, suivis de la façon dont ils pourraient être introduits dans votre programme.

(* sur une note plus technique. L'opérateur "IS" est vraiment utilisé comme confinement (ce qui n'est pas un opérateur logique). Quand je dis "A est un chevalier", je veux vraiment dire "A est un membre de l'ensemble de Chevaliers ". Le véritable opérateur utilisé serait 'ϵ'. Par souci de simplicité, nous utiliserons plutôt '='.)

J'utilise ce qui suit (vous pouvez utiliser n'importe quoi, tant qu'il est raisonnable et cohérent):

  • ^ ET
  • v OU
  • = EST
  • ~ NE PAS
  • => IMPLIES
  • X:La personne X prétend que ...

La personne Z peut faire n'importe quelle combinaison de l'un des types de déclarations suivants:

La personne Z dit que ...

  1. La personne A est un chevalier.

    Z: A = 1

  2. La personne Q est un Knave.

    Z: Q = 0

  3. Je suis chevalier.

    Z: Z = 1

  4. La personne A est un chevalier OU la personne B est un chevalier.

    Z: ( A = 1 ) v ( B = 1)

  5. La personne C est un chevalier ET je suis un chevalier.

    Z: ( C = 1 ) ^ ( Z = 0 )

  6. La personne R n'est PAS un chevalier.

    Z: ~( R = 1 )

En plus de cela, l'entrée peut également utiliser les lois de De Morgan

  1. Ce n'est PAS vrai que la personne A et la personne B sont des Knaves

    Z: ~( ( A = 0 ) ^ ( B = 0 ) )

  2. Il est faux que la personne A ou la personne B soit un chevalier

    Z: ~( ( A = 1 ) v ( B = 1) )

Enfin, les conditionnelles et leurs négations peuvent être utilisées

  1. Si je suis un chevalier, alors la personne B est un Knave

    Z: ( Z = 1 ) => ( B = 0 )

  2. Il n'est PAS vrai que si la personne B est un chevalier, alors la personne C est un chevalier.

    Z: ~( ( B = 1 ) => ( C = 0 ) )

Remarques sur les conditions

Consultez wikipedia pour plus d'informations.

Une instruction conditionnelle prend la forme p => q , où p et q sont eux-mêmes des instructions. p est "l'antécédent" et q est le "conséquent". Voici quelques informations utiles

  • La négation d'une condition ressemble à ceci: ~ (p => q) est équivalent à p ^ ~ q

  • Une fausse prémisse implique n'importe quoi. C'est-à-dire: si p est faux, alors toute déclaration p => q est vraie, quel que soit q . Par exemple: "si 2 + 2 = 5 alors je suis Spiderman" est une vraie affirmation.

Quelques cas de test simples

Ces cas sont donnés de la façon suivante (1) comment nous poserions le problème dans la parole (2) comment nous pourrions le poser à l'ordinateur (3) la sortie correcte que l'ordinateur pourrait donner.

  1. La personne A et la personne B vous approchent sur la route et font les déclarations suivantes:

    A: B est un chevalier ou je suis un chevalier.

    B: A est un chevalier.

Répondre:

B est un chevalier et A est un chevalier.

Contribution:

A B        # Cast of Characters
A: ( B = 0 ) v ( A = 1)
B: A = 1

Production:


    A = 1
    B = 1
    

  1. Les personnes A, B et F vous approchent sur la route et font les déclarations suivantes:

    R: Si je suis chevalier, alors B est un Knave.

    B: Si c'est vrai, alors F est aussi un Knave.

Répondre:

A est un chevalier, B est un chevalier, F est un chevalier.

Contribution

A B F
A: ( A = 1 ) => ( B = 0 )
B: ( A = 1 ) => ( F = 0 ) 

Production:


    A = 1
    B = 0
    F = 1
    

  1. Q, X et W vous approchent sur la route et font les déclarations suivantes:

    W: Il n'est pas vrai que Q et X soient tous deux chevaliers.

    Q: C'est vrai.

    X: Si ce que W dit est vrai, alors ce que Q dit est faux.

Répondre:

W et Q sont des chevaliers. X est un Knave.

Contribution

Q X W
W: ~( ( Q = 1 ) ^ ( X = 1 ) )
Q: W = 1
X: ( W = 1 ) => ( Q = 0 )

Production


    W = 1
    Q = 1
    X = 0
    

Il y a un défi similaire d'il y a 3 ans qui se concentre sur l'analyse et ne contient pas de conditions ni de De Morgan. Et donc, je dirais, est suffisamment différent dans la mise au point et la mise en œuvre pour éviter que cela ne soit dupe.

Ce défi a été brièvement clos en tant que dupe. Il a depuis été rouvert.

Je prétends que ce défi est d'abord différent. L'autre défi se concentre sur l'analyse syntaxique en anglais, ce n'est pas le cas. Deuxièmement, il utilise uniquement AND et OR alors que cela utilise des conditions et permet de résoudre de nombreux autres puzzles. À la fin de la journée, la question est de savoir si les réponses à ce défi peuvent être trivialement substituées à celle-ci, et je pense que l'inclusion des conditionnels et des négations conditionnelles ajoute une complexité suffisante pour que des changements plus robustes soient apportés afin pour relever ce défi.

Liam
la source
Que pouvons-nous conclure si un Knave dit (B=1)=>(C=0)? ~((B=1)=>(C=0))ou (B=1)=>(C=1)ou autre chose?
njpipeorgan
C'est impossible à faire en moins de 5 minutes. Ce problème est connu sous le nom de SAT et sa complexité est exponentielle. Ainsi pour n = 26 dans le cas général (pas 2 SAT), il est impossible de résoudre sur un ordinateur dans un délai raisonnable.
Labo
Votre premier cas de test a 2 sorties possibles. Comme vous utilisez la logique OR, cela pourrait être A:1 B:1ou A:1 B:0parce que les B B=1pourraient être faux tandis que A serait toujours vrai.
Katenkyo
@njpipeorgan Si le Knave est B, il ne peut pas le dire. Une fausse prémisse implique n'importe quoi et donc cette affirmation serait vraie. Si le Knave est un personnage différent, vous prendriez la négation, ce qui correspond (B=1)^(C=1)à la façon dont les conditions sont normalement traitées
Liam
1
Pour ceux qui se demandent, le vrai problème était parce que je regardais la requête d'entrée et qu'il regardait le puzzle formulé. Cela a été corrigé
Cameron Aavik

Réponses:

6

Python 3, 450 342 307 octets

Edit: il s'avère que j'ai oublié une importation ...

Ma première solution profite d'une dénomination flexible pour les requêtes

from functools import*
def g(c,r):c=c.split();l,y=len(c),range;d=[dict((c[i],n>>i&1)for i in y(l))for n in y(2**l)];return[n[1]for n in[[eval(reduce(lambda x,y:x.replace(y,str(d[i][y])),d[i],')and '.join(['not',''][d[i][s[0]]]+'('+s[2:].replace('->','<1or')for s in r)+')')),d[i]]for i in y(len(d))]if n[0]]

Vous pouvez appeler celui-ci avec

g('Q X W', ['W: not( ( Q == 1 ) and ( X == 1 ) )','Q: W == 1', 'X: ( W == 1 ) -> ( Q == 0 )'])

Le suivant ici utilise le même format de requêtes que celui vu dans l'OP, il n'a pas non plus certaines des modifications que j'ai apportées au premier. Il est de 417 octets car il convertit entre les deux formats.

from functools import*
def g(c,r):c=c.split();l,y=len(c),range;d=[{**dict((c[i],n>>i&1)for i in y(l)),**{'v':'or','^':'and','=':'==','~':'not'}}for n in y(2**l)];f=lambda r,c:reduce(lambda x,y:x.replace(y,str(c[y])),c,('(0<1'+''.join([')^ '+['~',''][c[t[0]]]+'('+t[1]for t in[s.split(":")for s in r]])+')').replace('=>','<1or'));return[dict((o,j) for o,j in n[0].items() if o in c) for n in[[d[i],eval(f(r,d[i]))]for i in y(len(d))]if n[1]]

Et il peut être appelé par:

g('Q X W', ['W: ~( ( Q = 1 ) ^ ( X = 1 ) )','Q: W = 1', 'X: ( W = 1 ) => ( Q = 0 )'])

Ils reviennent tous les deux

[{'X': 0, 'W': 1, 'Q': 1}]

Explication non golfée:

from functools import *
def knight_and_knaves(cast,rules):
    # turns 'A B C' into ['A','B','C']
    cast = cast.split()
    # gets all numbers that can fit in len(cast) bits
    bitmasks = range(2 ** len(cast))
    # for every bitmask, apply the value for a bit to the boolean value for each cast member.
    # This returns a dictionary of all possible outcomes.
    d=[dict((cast[i], n>>i & 1) for i in range(len(cast))) for n in bitmasks]
    # Split rules at colon
    rules = [s.split(":")for s in rules]
    # Turns list of rules into one python expression, joins each rule with ')and ', maybe a 'not' depending on if the hypothesis has the rule as a lie, and '('.
    # Also replaces '->' with '<1or' which is equivalent to it. Also starts with '(True' and ends with ')' to resolve missing parentheses
    transform_rules = lambda d, rules: ('(True' + ''.join([')and ' + ['not', ''][d[rule[0]]] + '(' + rule[1].replace('->','<1or') for rule in rules]) + ')')
    # Applys transform_rules on each outcome and evaluates the result, storing it into a list of lists where each element is [outcome, value]
    outcomes=[[d[i],eval(reduce(lambda x,y:x.replace(y,str(d[i][y])),d[i],transform_rules(d[i], rules)))] for i in range(len(d))]
    # Filters outcomes if value is True
    return[n[0]for n in outcomes if n[1]]

En outre, la deuxième solution nécessite 3,5 (et non 3,4) en raison de l'utilisation de PEP 448

Cameron Aavik
la source
1

Mathematica, 80 octets

F[c_,s_]:=Select[Thread[c->#]&/@{True,False}~Tuples~Length@c,And@@Equal@@@s/.#&]

Explication

La fonction Fprend deux arguments,

  • c est une liste des noms de tous les personnages,
  • s est une liste de déclarations, chacune contenant deux parties - qui dit quoi.

Par exemple, il y a trois caractères, Q, X et W.

characters={q,x,w};

Et ils disent:

statements=
   {{w, !((q==True)&&(x==True))   },
    {q, w==True                   },
    {x, Implies[w==True,q==False] }};

Trueet Falsesignifie Chevaliers et Knaves respectivement. alors

F[characters, statements]

donnera {{q->True, x->False, w->True}}, ce qui signifie qu'il n'y a qu'une seule solution que Q et W sont des chevaliers tandis que X est un Knave. S'il existe plusieurs solutions, la sortie ressemblera à{{...},{...},...}


L'algorithme est très simple. {True,False}~Tuples~Length@cdonne toutes les combinaisons possibles de chevaliers et de knaves parmi les personnages. Thread[c->#]&/@Construisez ensuite un tableau de règles basé sur ces combinaisons. Dans le cas de deux caractères A et B, le tableau sera

{{a->True, b->True },
 {a->True, b->False},
 {a->False,b->True },
 {a->False,b->False}}

En remplaçant les instructions par une ligne de ces règles, nous obtiendrons un tableau comme

{{True,True},{True,False},{False,False}}

La première colonne de ce tableau est l'identité des locuteurs, et la deuxième colonne indique si leurs déclarations sont vraies ou fausses. Une solution valable nécessite la concordance entre l'identité des locuteurs et leurs déclarations. Le tableau ci-dessus signifie que cette combinaison n'est pas une solution, car le deuxième haut-parleur, un Knight, fait une déclaration incorrecte.

Select[...,And@@Equal@@@s/.#&]

effectue les substitutions et sélectionne les combinaisons qui satisfont à la condition.

njpipeorgan
la source
1

Rubis, 128

C'est le même algorithme que tout le monde, essayez toutes les combinaisons possibles de chevaliers et de chevaliers et voyez quels bâtons. J'en ai un autre sur lequel je travaille, mais je pense que ce sera plus long (bien que plus intéressant).

Les entrées de l'instruction doivent être:

  • & ET
  • | OU
  • == EST
  • ! NE PAS
  • > IMPLIES
  • X: La personne X prétend que ...

J'exige également que chaque instruction et sous-instruction soit entre parenthèses. Le seul problème avec cette version est qu'elle passe par 2 ^ 26 itérations au maximum, et si ce ne sont pas tous des knaves, au moins 2 ^ (26-n) itérations ! Pour mettre cela en perspective, cela signifie que s'il y a deux personnes, et au moins une n'est pas un valet, cela prendra un minimum de 16 777 216 itérations !

Pour limiter cela, je soumets ce qui suit à 168 octets. Remplacez-le 26pour #{o.size}le ramener à 161.

->s{o=s[/.*?$/].split
i=0
eval h=o.zip(("%0#{o.size}b"%i+=1).chars).map{|k|k*?=}*?;until h&&o.all?{|t|!s[/#{t}:(.*)$/]||eval("(#{t}<1)^(#{$1.gsub(?>,'!=true||')})")}
h}

Mais si je peux utiliser à la place un tableau de personnes et une carte d'instructions, par exemple:

c[[?A, ?B],
  {
    ?A=> "( B == 0 ) | ( A == 1)",
    ?B=> "A == 1"
  }
 ]

Ensuite, je descends à 128.

->o,s{i=0
eval h=o.zip(("%026b"%i+=1).chars).map{|k|k*?=}*?;until h&&s.all?{|t,k|eval("(#{t}<1)^(#{k.gsub(?>,'!=true||')})")}
h}
Pas que Charles
la source