Contexte
Je joue régulièrement à D&D avec des amis. Tout en parlant de la complexité de certains systèmes / versions lorsqu'il s'agit de lancer des dés et d'appliquer des bonus et des pénalités, nous avons proposé en plaisantant une certaine complexité supplémentaire pour les expressions de roulement de dés. Certains d'entre eux étaient trop scandaleux (comme l'extension d'expressions de dés simples comme 2d6
aux arguments de matrice 1 ), mais les autres constituent un système intéressant.
Le défi
Étant donné une expression de dés complexe, évaluez-la selon les règles suivantes et sortez le résultat.
Règles d'évaluation de base
- Chaque fois qu'un opérateur attend un entier mais reçoit une liste pour un opérande, la somme de cette liste est utilisée
- Chaque fois qu'un opérateur attend une liste mais reçoit un entier pour un opérande, l'entier est traité comme une liste à un élément contenant cet entier
Les opérateurs
Tous les opérateurs sont des opérateurs d'infixes binaires. Aux fins d'explication, a
sera l'opérande gauche et b
sera l'opérande droit. La notation de liste sera utilisée pour des exemples où les opérateurs peuvent prendre des listes comme opérandes, mais les expressions réelles ne sont constituées que d'entiers positifs et d'opérateurs.
d
: sortie d'a
entiers aléatoires uniformes indépendants dans la plage[1, b]
- Priorité: 3
- Les deux opérandes sont des entiers
- Exemples:
3d4 => [1, 4, 3]
,[1, 2]d6 => [3, 2, 6]
t
: prendre lesb
valeurs les plus faibles dea
- Priorité: 2
a
est une liste,b
est un entier- Si
b > len(a)
, toutes les valeurs sont retournées - Exemples:
[1, 5, 7]t1 => [1]
,[5, 18, 3, 9]t2 => [3, 5]
,3t5 => [3]
T
: prendre lesb
valeurs les plus élevées dea
- Priorité: 2
a
est une liste,b
est un entier- Si
b > len(a)
, toutes les valeurs sont retournées - Exemples:
[1, 5, 7]T1 => [7]
,[5, 18, 3, 9]T2 => [18, 9]
,3T5 => [3]
r
: si des élémentsb
sonta
dedans, relancez ces éléments, en utilisant lad
déclaration qui les a générés- Priorité: 2
- Les deux opérandes sont des listes
- Le relancement n'est effectué qu'une seule fois, il est donc possible d'avoir encore des éléments
b
dans le résultat - Exemples:
3d6r1 => [1, 3, 4] => [6, 3, 4]
,2d4r2 => [2, 2] => [3, 2]
,3d8r[1,8] => [1, 8, 4] => [2, 2, 4]
R
: si des élémentsb
sont dansa
, relancez ces éléments à plusieurs reprises jusqu'à ce qu'aucun élément deb
ne soit présent, en utilisant lad
déclaration qui les a générés- Priorité: 2
- Les deux opérandes sont des listes
- Exemples:
3d6R1 => [1, 3, 4] => [6, 3, 4]
,2d4R2 => [2, 2] => [3, 2] => [3, 1]
,3d8R[1,8] => [1, 8, 4] => [2, 2, 4]
+
: ajoutera
etb
ensemble- Priorité: 1
- Les deux opérandes sont des entiers
- Exemples:
2+2 => 4
,[2]+[2] => 4
,[3, 1]+2 => 6
-
: soustraireb
dea
- Priorité: 1
- Les deux opérandes sont des entiers
b
sera toujours inférieur àa
- Exemples:
2-1 => 1
,5-[2] => 3
,[8, 3]-1 => 10
.
: concaténera
etb
ensemble- Priorité: 1
- Les deux opérandes sont des listes
- Exemples:
2.2 => [2, 2]
,[1].[2] => [1, 2]
,3.[4] => [3, 4]
_
: sortiea
avec tous les élémentsb
supprimés- Priorité: 1
- Les deux opérandes sont des listes
- Exemples:
[3, 4]_[3] => [4]
,[2, 3, 3]_3 => [2]
,1_2 => [1]
Règles supplémentaires
- Si la valeur finale d'une expression est une liste, elle est additionnée avant la sortie
- L'évaluation des termes ne produira que des entiers positifs ou des listes d'entiers positifs - toute expression qui aboutit à un entier non positif ou une liste contenant au moins un entier non positif verra ces valeurs remplacées par
1
s - Les parenthèses peuvent être utilisées pour regrouper les termes et spécifier l'ordre d'évaluation
- Les opérateurs sont évalués par ordre de priorité la plus élevée à la priorité la plus basse, l'évaluation se déroulant de gauche à droite dans le cas d'une priorité liée (ainsi
1d4d4
serait évalué comme(1d4)d4
) - L'ordre des éléments dans les listes n'a pas d'importance - il est parfaitement acceptable pour un opérateur qui modifie une liste de la renvoyer avec ses éléments dans un ordre relatif différent
- Les termes qui ne peuvent pas être évalués ou qui entraîneraient une boucle infinie (comme
1d1R1
ou3d6R[1, 2, 3, 4, 5, 6]
) ne sont pas valides
Cas de test
Format: input => possible output
1d20 => 13
2d6 => 8
4d6T3 => 11
2d20t1 => 13
5d8r1 => 34
5d6R1 => 20
2d6d6 => 23
3d2R1d2 => 3
(3d2R1)d2 => 11
1d8+3 => 10
1d8-3 => 4
1d6-1d2 => 2
2d6.2d6 => 12
3d6_1 => 8
1d((8d20t4T2)d(6d6R1r6)-2d4+1d2).(1d(4d6_3d3)) => 61
Tous, sauf le dernier cas de test, ont été générés avec l'implémentation de référence.
Exemple travaillé
Expression: 1d((8d20t4T2)d(6d6R1r6)-2d4+1d2).(1d(4d6_3d3))
8d20t4T2 => [19, 5, 11, 6, 19, 15, 4, 20]t4T2 => [4, 5, 6, 11]T2 => [11, 6]
(plein:1d(([11, 6])d(6d6R1r6)-2d4+1d2).(1d(4d6_3d3))
)6d6R1r6 => [2, 5, 1, 5, 2, 3]r1R6 => [2, 5, 3, 5, 2, 3]R6 => [2, 5, 3, 5, 2, 3]
(1d([11, 6]d[2, 5, 3, 5, 2, 3]-2d4+1d2).(1d(4d6_3d3))
)[11, 6]d[2, 5, 3, 5, 2, 3] => 17d20 => [1, 6, 11, 7, 2, 8, 15, 3, 4, 18, 11, 11, 1, 10, 8, 6, 11]
(1d([1, 6, 11, 7, 2, 8, 15, 3, 4, 18, 11, 11, 1, 10, 8, 6, 11]-2d4+1d2).(1d(4d6_3d3))
)2d4 => 7
(1d([1, 6, 11, 7, 2, 8, 15, 3, 4, 18, 11, 11, 1, 10, 8, 6, 11]-7+1d2).(1d(4d6_3d3))
)1d2 => 2
(1d([1, 6, 11, 7, 2, 8, 15, 3, 4, 18, 11, 11, 1, 10, 8, 6, 11]-7+2).(1d(4d6_3d3))
)[1, 6, 11, 7, 2, 8, 15, 3, 4, 18, 11, 11, 1, 10, 8, 6, 11]-7+2 => 133-7+2 => 128
(1d128).(1d(4d6_3d3))
)4d6_3d3 => [1, 3, 3, 6]_[3, 2, 2] => [1, 3, 3, 6, 3, 2, 2]
(1d128).(1d[1, 3, 3, 6, 3, 2, 2])
)1d[1, 3, 3, 6, 3, 2, 2] => 1d20 => 6
(1d128).(6)
)1d128 => 55
(55.6
)55.6 => [55, 6]
([55, 6]
)[55, 6] => 61
(terminé)
Implémentation de référence
Cette implémentation de référence utilise la même graine constante ( 0
) pour évaluer chaque expression pour des sorties cohérentes testables. Il attend une entrée sur STDIN, avec des retours à la ligne séparant chaque expression.
#!/usr/bin/env python3
import re
from random import randint, seed
from collections import Iterable
from functools import total_ordering
def as_list(x):
if isinstance(x, Iterable):
return list(x)
else:
return [x]
def roll(num_sides):
return Die(randint(1, num_sides), num_sides)
def roll_many(num_dice, num_sides):
num_dice = sum(as_list(num_dice))
num_sides = sum(as_list(num_sides))
return [roll(num_sides) for _ in range(num_dice)]
def reroll(dice, values):
dice, values = as_list(dice), as_list(values)
return [die.reroll() if die in values else die for die in dice]
def reroll_all(dice, values):
dice, values = as_list(dice), as_list(values)
while any(die in values for die in dice):
dice = [die.reroll() if die in values else die for die in dice]
return dice
def take_low(dice, num_values):
dice = as_list(dice)
num_values = sum(as_list(num_values))
return sorted(dice)[:num_values]
def take_high(dice, num_values):
dice = as_list(dice)
num_values = sum(as_list(num_values))
return sorted(dice, reverse=True)[:num_values]
def add(a, b):
a = sum(as_list(a))
b = sum(as_list(b))
return a+b
def sub(a, b):
a = sum(as_list(a))
b = sum(as_list(b))
return max(a-b, 1)
def concat(a, b):
return as_list(a)+as_list(b)
def list_diff(a, b):
return [x for x in as_list(a) if x not in as_list(b)]
@total_ordering
class Die:
def __init__(self, value, sides):
self.value = value
self.sides = sides
def reroll(self):
self.value = roll(self.sides).value
return self
def __int__(self):
return self.value
__index__ = __int__
def __lt__(self, other):
return int(self) < int(other)
def __eq__(self, other):
return int(self) == int(other)
def __add__(self, other):
return int(self) + int(other)
def __sub__(self, other):
return int(self) - int(other)
__radd__ = __add__
__rsub__ = __sub__
def __str__(self):
return str(int(self))
def __repr__(self):
return "{} ({})".format(self.value, self.sides)
class Operator:
def __init__(self, str, precedence, func):
self.str = str
self.precedence = precedence
self.func = func
def __call__(self, *args):
return self.func(*args)
def __str__(self):
return self.str
__repr__ = __str__
ops = {
'd': Operator('d', 3, roll_many),
'r': Operator('r', 2, reroll),
'R': Operator('R', 2, reroll_all),
't': Operator('t', 2, take_low),
'T': Operator('T', 2, take_high),
'+': Operator('+', 1, add),
'-': Operator('-', 1, sub),
'.': Operator('.', 1, concat),
'_': Operator('_', 1, list_diff),
}
def evaluate_dice(expr):
return max(sum(as_list(evaluate_rpn(shunting_yard(tokenize(expr))))), 1)
def evaluate_rpn(expr):
stack = []
while expr:
tok = expr.pop()
if isinstance(tok, Operator):
a, b = stack.pop(), stack.pop()
stack.append(tok(b, a))
else:
stack.append(tok)
return stack[0]
def shunting_yard(tokens):
outqueue = []
opstack = []
for tok in tokens:
if isinstance(tok, int):
outqueue = [tok] + outqueue
elif tok == '(':
opstack.append(tok)
elif tok == ')':
while opstack[-1] != '(':
outqueue = [opstack.pop()] + outqueue
opstack.pop()
else:
while opstack and opstack[-1] != '(' and opstack[-1].precedence > tok.precedence:
outqueue = [opstack.pop()] + outqueue
opstack.append(tok)
while opstack:
outqueue = [opstack.pop()] + outqueue
return outqueue
def tokenize(expr):
while expr:
tok, expr = expr[0], expr[1:]
if tok in "0123456789":
while expr and expr[0] in "0123456789":
tok, expr = tok + expr[0], expr[1:]
tok = int(tok)
else:
tok = ops[tok] if tok in ops else tok
yield tok
if __name__ == '__main__':
import sys
while True:
try:
dice_str = input()
seed(0)
print("{} => {}".format(dice_str, evaluate_dice(dice_str)))
except EOFError:
exit()
[1]: Notre définition des adb
arguments pour la matrice était de rouler AdX
pour chacun X
dans a * b
, où A = det(a * b)
. De toute évidence, c'est trop absurde pour ce défi.
-
ceb
sera toujours moins quea
je ne vois aucun moyen d'obtenir des entiers non positifs, donc la deuxième règle supplémentaire semble inutile. OTOH,_
pourrait entraîner une liste vide, ce qui semble utile dans les mêmes cas, mais qu'est-ce que cela signifie lorsqu'un entier est nécessaire? Normalement, je dirais que la somme est0
...0
. Selon la règle du non-positif, il serait évalué comme a1
.[1,2]_([1]_[1])
est[1,2]
?[2]
, car[1]_[1] -> [] -> 0 -> 1 -> [1]
.Réponses:
Python 3,
803 788 753 749 744 748 745 740 700 695682 octets-5 octets grâce à Mr.Xcoder
-5 octets de plus grâce à NGN
-environ 40 octets grâce à Jonathan French
Beurk, quelle blague! Cela fonctionne en utilisant une expression régulière pour encapsuler tous les nombres de ma
k
classe, et en convertissant tous les opérateurs en opérateurs que python comprend, puis en utilisant les méthodes magiques de lak
classe pour gérer les mathématiques. Le+-
et+--
à la fin de.
et_
sont un hack pour garder la priorité correcte. De même, je ne peux pas utiliser l'**
opérateur pour d car cela ferait1d4d4
être analysé comme1d(4d4)
. Au lieu de cela, j'encapsule tous les nombres dans un ensemble supplémentaire de parens et fais d as.j
, car les appels de méthode ont une priorité plus élevée que les opérateurs. La dernière ligne est évaluée comme une fonction anonyme qui évalue l'expression.la source
def __mod__(a, b)
... Pourquoi l'espace entrea,
etb
?; __sub__
. Et peut - être aussi ici:lambda a,b: l(
.exec("""...""".replace("...","..."))
instruction et en remplaçant les chaînes qui se produisent souvent (commereturn
) par un seul caractère. Cependant, pour moi laexec
-stratégie semble toujours un peu peu élégante ...__mod__
et__add__
ne ont pas besoin que tiret beaucoupAPL (Dyalog Classic) , 367 octets
Essayez-le en ligne!
Il s'agit de l'algorithme de triage de l'implémentation de référence fusionné
evaluate_dice()
, sans le non-sens cru et orienté objet. Seules deux piles sont utilisées:h
pour les opérateurs etv
pour les valeurs. L'analyse et l'évaluation sont entrelacées.Les résultats intermédiaires sont représentés sous la forme de matrices 2 × N où la première ligne représente les valeurs aléatoires et la deuxième ligne le nombre de côtés sur les dés qui les ont produites. Lorsqu'un résultat n'est pas produit par l'opérateur "d" qui lance des dés, la deuxième ligne contient des nombres arbitraires. Une seule valeur aléatoire est une matrice 2 × 1 et donc indiscernable d'une liste à 1 élément.
la source
Python 3:
723722714711707675653665 octetsLe point d'entrée est
E
. Cela applique les expressions régulières de manière itérative. Tout d'abord, il remplace tous les entiersx
par un tuple de liste singleton[(x,0)]
. Ensuite, la première expression régulière effectue l'd
opération, en remplaçant all[(x,0)]d[(b,0)]
par la représentation sous forme de chaîne d'un tableau de tuples comme[(1,b),(2,b),(3,b)]
. Le deuxième élément de chaque tuple est le deuxième opérande ded
. Ensuite, les expressions régulières suivantes exécutent chacun des autres opérateurs. Il existe une expression rationnelle spéciale pour supprimer les parens des expressions entièrement calculées.la source
Clojure,
731720 octets(lorsque les sauts de ligne sont supprimés)
Mise à jour: une implémentation plus courte de
F
.Il se compose de quatre parties principales:
N
: contraint une liste en un nombreg
: évalue un arbre de syntaxe abstraite (expressions S avec 3 éléments)F
: convertit un infixe AST en notation de préfixe (expressions S), applique également la priorité de l'ordre des opérandesf
: utiliseread-string
pour convertir une chaîne en une séquence imbriquée de nombres et de symboles (infixe AST), les redirige vers F -> g -> N, renvoyant le numéro de résultat.Je ne sais pas comment tester cela à fond, peut-être via des tests statistiques contre une implémentation de référence? Au moins l'AST et son évaluation sont relativement faciles à suivre.
Exemple d'expression S de
1d((8d20t4T2)d(6d6R1r6)-2d4+1d2).(1d(4d6_3d3))
:Moins de golf avec des résultats et des tests internes:
la source
Python 3, 695 octets
Un interpréteur construit à l'aide d'
tatsu
une bibliothèque d'analyseur PEG. Le premier argument detatsu.parser()
est la grammaire PEG.class D
(pour Die) sous-classe leint
type intégré . Sa valeur est le résultat d'un rouleau. L'attribut.s
est le nombre de faces du dé.class S
a les actions sémantiques pour l'analyseur et implémente l'interpréteur.la source