Fondo
Juego D&D regularmente con algunos amigos. Mientras hablamos de la complejidad de algunos sistemas / versiones cuando se trata de lanzar dados y aplicar bonos y penalizaciones, bromeamos con cierta complejidad adicional para las expresiones de lanzamiento de dados. Algunos de ellos eran demasiado escandalosos (como extender expresiones simples de dados, como los 2d6
argumentos de matriz 1 ), pero el resto crea un sistema interesante.
El reto
Dada una expresión de dados compleja, evalúela de acuerdo con las siguientes reglas y genere el resultado.
Reglas básicas de evaluación
- Siempre que un operador espera un número entero pero recibe una lista para un operando, se utiliza la suma de esa lista
- Cada vez que un operador espera una lista pero recibe un entero para un operando, el entero se trata como una lista de un elemento que contiene ese entero
Operadores
Todos los operadores son operadores infix binarios. Para fines de explicación, a
será el operando izquierdo y b
será el operando derecho. La notación de lista se usará para ejemplos en los que los operadores pueden tomar listas como operandos, pero las expresiones reales consisten solo en enteros positivos y operadores.
d
: salida dea
enteros aleatorios uniformes independientes en el rango[1, b]
- Precedencia: 3
- Ambos operandos son enteros.
- Ejemplos:
3d4 => [1, 4, 3]
,[1, 2]d6 => [3, 2, 6]
t
: toma losb
valores más bajos dea
- Precedencia: 2
a
es una lista,b
es un entero- Si
b > len(a)
, se devuelven todos los valores - Ejemplos:
[1, 5, 7]t1 => [1]
,[5, 18, 3, 9]t2 => [3, 5]
,3t5 => [3]
T
: toma losb
valores más altos dea
- Precedencia: 2
a
es una lista,b
es un entero- Si
b > len(a)
, se devuelven todos los valores - Ejemplos:
[1, 5, 7]T1 => [7]
,[5, 18, 3, 9]T2 => [18, 9]
,3T5 => [3]
r
: Si cualquier elemento enb
están ena
, volver a tirar esos elementos, usando cualquierd
declaración que genera- Precedencia: 2
- Ambos operandos son listas
- El cambio de rol se realiza solo una vez, por lo que aún es posible tener elementos de
b
en el resultado - Ejemplos:
3d6r1 => [1, 3, 4] => [6, 3, 4]
,2d4r2 => [2, 2] => [3, 2]
,3d8r[1,8] => [1, 8, 4] => [2, 2, 4]
R
: Si cualquier elemento enb
están ena
, volver a tirar los elementos repetidamente hasta que no hay elementos deb
están presentes, usando cualquierd
declaración que genera- Precedencia: 2
- Ambos operandos son listas
- Ejemplos:
3d6R1 => [1, 3, 4] => [6, 3, 4]
,2d4R2 => [2, 2] => [3, 2] => [3, 1]
,3d8R[1,8] => [1, 8, 4] => [2, 2, 4]
+
: agregara
yb
juntos- Precedencia: 1
- Ambos operandos son enteros.
- Ejemplos:
2+2 => 4
,[2]+[2] => 4
,[3, 1]+2 => 6
-
: restarb
dea
- Precedencia: 1
- Ambos operandos son enteros.
b
siempre será menor quea
- Ejemplos:
2-1 => 1
,5-[2] => 3
,[8, 3]-1 => 10
.
: concatenara
yb
juntos- Precedencia: 1
- Ambos operandos son listas
- Ejemplos:
2.2 => [2, 2]
,[1].[2] => [1, 2]
,3.[4] => [3, 4]
_
: salidaa
con todos los elementos deb
eliminado- Precedencia: 1
- Ambos operandos son listas
- Ejemplos:
[3, 4]_[3] => [4]
,[2, 3, 3]_3 => [2]
,1_2 => [1]
Reglas Adicionales
- Si el valor final de una expresión es una lista, se suma antes de la salida
- La evaluación de los términos solo dará como resultado enteros positivos o listas de enteros positivos: cualquier expresión que dé como resultado un entero no positivo o una lista que contenga al menos un entero no positivo tendrá esos valores reemplazados por
1
s - Los paréntesis pueden usarse para agrupar términos y especificar el orden de evaluación
- Los operadores se evalúan en orden de precedencia más alta a precedencia más baja, y la evaluación se realiza de izquierda a derecha en el caso de precedencia vinculada (por
1d4d4
lo que se evaluaría como(1d4)d4
) - El orden de los elementos en las listas no importa: es perfectamente aceptable que un operador que modifica una lista la devuelva con sus elementos en un orden relativo diferente
- Los términos que no pueden evaluarse o darían lugar a un bucle infinito (como
1d1R1
o3d6R[1, 2, 3, 4, 5, 6]
) no son válidos
Casos de prueba
Formato: 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
Todos menos el último caso de prueba se generaron con la implementación de referencia.
Ejemplo trabajado
Expresión: 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]
(completo: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
(hecho)
Implementación de referencia
Esta implementación de referencia utiliza la misma semilla constante ( 0
) para evaluar cada expresión para resultados comprobables y consistentes. Espera entrada en STDIN, con nuevas líneas que separan cada expresión.
#!/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]: Nuestra definición de adb
argumentos de matriz era rodar AdX
para cada X
en a * b
, donde A = det(a * b)
. Claramente eso es demasiado absurdo para este desafío.
-
queb
siempre será menor dea
lo que no veo forma de obtener enteros no positivos, por lo que la segunda regla adicional parece inútil. OTOH,_
podría dar como resultado una lista vacía, que parece útil en los mismos casos, pero ¿qué significa cuando se necesita un número entero? Normalmente diría que la suma es0
...0
. Por la regla de no no positivos, se evaluaría como a1
.[1,2]_([1]_[1])
es[1,2]
?[2]
, porque[1]_[1] -> [] -> 0 -> 1 -> [1]
.Respuestas:
Python 3,
803 788 753 749 744 748 745 740 700 695682 bytes-5 bytes gracias a Mr.Xcoder
-5 bytes más gracias a NGN
-aproximadamente 40 bytes gracias a Jonathan French
¡Qué asco, qué tontería! Esto funciona mediante el uso de una expresión regular para ajustar todos los números en mi
k
clase, y la conversión de todos los operadores en operadores que Python entiende, y luego utilizando los métodos mágicos de lak
clase para manejar las matemáticas. El+-
y+--
al final para.
y_
son un truco para mantener la precedencia correcta. Del mismo modo, no puedo usar el**
operador para d porque al hacerlo1d4d4
se analizaría como1d(4d4)
. En cambio, envuelvo todos los números en un conjunto adicional de parens y hago d as.j
, ya que las llamadas a métodos tienen mayor prioridad que los operadores. La última línea se evalúa como una función anónima que evalúa la expresión.fuente
def __mod__(a, b)
... ¿Por qué el espacio entrea,
yb
?; __sub__
. Y posiblemente también aquí:lambda a,b: l(
.exec("""...""".replace("...","..."))
declaración y reemplazando las cadenas que ocurren a menudo (comoreturn
) con un solo carácter. Sin embargo, para mí laexec
estrategia siempre parece un poco poco elegante ...__mod__
y__add__
no necesitan que tanto guiónAPL (Dyalog Classic) , 367 bytes
Pruébalo en línea!
Este es el algoritmo de yardas de derivación de la implementación de referencia fusionada con
evaluate_dice()
, sin la tontería cruda y orientada a objetos. Solo se utilizan dos pilas:h
para los operadores yv
para los valores. El análisis y la evaluación están intercalados.Los resultados intermedios se representan como matrices 2 × N, donde la primera fila son los valores aleatorios y la segunda fila es el número de lados en los dados que los produjeron. Cuando el operador "d" que lanza los dados no produce un resultado, la segunda fila contiene números arbitrarios. Un único valor aleatorio es una matriz 2 × 1 y, por lo tanto, no se puede distinguir de una lista de 1 elemento.
fuente
Python 3:
723722714711707675653665 bytesEl punto de entrada es
E
. Esto aplica expresiones regulares de forma iterativa. Primero reemplaza todos los enterosx
con una tupla de lista singleton[(x,0)]
. Luego, la primera expresión regular realiza lad
operación, reemplazando todo[(x,0)]d[(b,0)]
con la representación de cadena de una matriz de tuplas como[(1,b),(2,b),(3,b)]
. El segundo elemento de cada tupla es el segundo operando ad
. Luego, las expresiones regulares posteriores realizan cada uno de los otros operadores. Hay una expresión regular especial para eliminar parens de expresiones totalmente calculadas.fuente
Clojure,
731720 bytes(cuando se eliminan nuevas líneas)
Actualización: una implementación más corta de
F
.Este consta de cuatro partes principales:
N
: coacciona una lista en un númerog
: evalúa un árbol de sintaxis abstracta (expresiones S con 3 elementos)F
: convierte un AST infijo en notación de prefijo (expresiones S), también aplica la prioridad de orden de operandof
: usosread-string
para convertir una cadena en una secuencia anidada de números y símbolos (infijo AST), los canaliza a través de F -> g -> N, devolviendo el número de resultado.No estoy seguro de cómo probar esto a fondo, ¿tal vez a través de pruebas estadísticas contra una implementación de referencia? Al menos el AST y su evaluación son relativamente fáciles de seguir.
Ejemplo de expresión S de
1d((8d20t4T2)d(6d6R1r6)-2d4+1d2).(1d(4d6_3d3))
:Menos golf con resultados y pruebas de internado:
fuente
Python 3, 695 bytes
Un intérprete creado con
tatsu
una biblioteca de analizador PEG. El primer argumentotatsu.parser()
es la gramática PEG.class D
(para Die) subclases delint
tipo incorporado . Su valor es el resultado de un rollo. El atributo.s
es el número de lados en el dado.class S
tiene las acciones semánticas para el analizador e implementa el intérprete.fuente