Hintergrund
Ich spiele regelmäßig D & D mit einigen Freunden. Während wir über die Komplexität einiger Systeme / Versionen beim Würfeln und Anwenden von Boni und Strafen sprachen, kamen wir scherzhaft auf eine zusätzliche Komplexität für das Würfeln von Ausdrücken. Einige von ihnen waren zu empörend (wie das Erweitern einfacher Würfelausdrücke wie 2d6
Matrixargumente 1 ), aber der Rest ergibt ein interessantes System.
Die Herausforderung
Bewerten Sie einen komplexen Würfelausdruck anhand der folgenden Regeln und geben Sie das Ergebnis aus.
Grundlegende Bewertungsregeln
- Wann immer ein Operator eine Ganzzahl erwartet, aber eine Liste für einen Operanden erhält, wird die Summe dieser Liste verwendet
- Wann immer ein Operator eine Liste erwartet, aber eine Ganzzahl für einen Operanden empfängt, wird die Ganzzahl als eine Liste mit einem Element behandelt, die diese Ganzzahl enthält
Betreiber
Alle Operatoren sind binäre Infixoperatoren. Zum Zwecke der Erklärung a
wird der linke Operand und b
der rechte Operand sein. Die Listennotation wird für Beispiele verwendet, in denen Operatoren Listen als Operanden verwenden können, tatsächliche Ausdrücke jedoch nur aus positiven ganzen Zahlen und Operatoren bestehen.
d
:a
Unabhängige, einheitliche Zufallszahlen im Bereich ausgeben[1, b]
- Vorrang: 3
- Beide Operanden sind ganze Zahlen
- Beispiele:
3d4 => [1, 4, 3]
,[1, 2]d6 => [3, 2, 6]
t
: nimm dieb
niedrigsten Werte vona
- Vorrang: 2
a
ist eine Liste,b
ist eine ganze Zahl- Wenn
b > len(a)
, werden alle Werte zurückgegeben - Beispiele:
[1, 5, 7]t1 => [1]
,[5, 18, 3, 9]t2 => [3, 5]
,3t5 => [3]
T
: nimm dieb
höchsten Werte vona
- Vorrang: 2
a
ist eine Liste,b
ist eine ganze Zahl- Wenn
b > len(a)
, werden alle Werte zurückgegeben - Beispiele:
[1, 5, 7]T1 => [7]
,[5, 18, 3, 9]T2 => [18, 9]
,3T5 => [3]
r
: Wenn Elemente in enthaltenb
sinda
, rollen Sie diese Elemente mit derd
generierten Anweisung erneut- Vorrang: 2
- Beide Operanden sind Listen
- Das erneute Rollen wird nur einmal durchgeführt, daher ist es möglich, noch Elemente
b
im Ergebnis zu haben - Beispiele:
3d6r1 => [1, 3, 4] => [6, 3, 4]
,2d4r2 => [2, 2] => [3, 2]
,3d8r[1,8] => [1, 8, 4] => [2, 2, 4]
R
: Wenn Elemente in enthaltenb
sinda
, wiederholen Sie diese Elemente, bis keine Elemente vonb
vorhanden sind, und verwenden Sie dabeid
die von ihnen generierte Anweisung- Vorrang: 2
- Beide Operanden sind Listen
- Beispiele:
3d6R1 => [1, 3, 4] => [6, 3, 4]
,2d4R2 => [2, 2] => [3, 2] => [3, 1]
,3d8R[1,8] => [1, 8, 4] => [2, 2, 4]
+
: addierena
undb
zusammen- Vorrang: 1
- Beide Operanden sind ganze Zahlen
- Beispiele:
2+2 => 4
,[2]+[2] => 4
,[3, 1]+2 => 6
-
: subtrahierenb
vona
- Vorrang: 1
- Beide Operanden sind ganze Zahlen
b
wird immer kleiner sein alsa
- Beispiele:
2-1 => 1
,5-[2] => 3
,[8, 3]-1 => 10
.
: verkettena
undb
zusammen- Vorrang: 1
- Beide Operanden sind Listen
- Beispiele:
2.2 => [2, 2]
,[1].[2] => [1, 2]
,3.[4] => [3, 4]
_
: Ausgabea
mit allen Elementen vonb
entfernt- Vorrang: 1
- Beide Operanden sind Listen
- Beispiele:
[3, 4]_[3] => [4]
,[2, 3, 3]_3 => [2]
,1_2 => [1]
Zusätzliche Regeln
- Wenn der Endwert eines Ausdrucks eine Liste ist, wird er vor der Ausgabe summiert
- Die Auswertung von Begriffen führt nur zu positiven Ganzzahlen oder Listen positiver Ganzzahlen. Bei jedem Ausdruck, der zu einer nicht positiven Ganzzahl oder einer Liste mit mindestens einer nicht positiven Ganzzahl führt, werden diese Werte durch
1
s ersetzt - Klammern können verwendet werden, um Begriffe zu gruppieren und die Reihenfolge der Auswertung festzulegen
- Operatoren werden in der Reihenfolge der höchsten bis zur niedrigsten Priorität ausgewertet, wobei die Auswertung bei gebundener Priorität von links nach rechts erfolgt (wird also
1d4d4
ausgewertet als(1d4)d4
). - Die Reihenfolge der Elemente in Listen spielt keine Rolle. Es ist durchaus akzeptabel, dass ein Operator, der eine Liste ändert, sie mit ihren Elementen in einer anderen relativen Reihenfolge zurückgibt
- Begriffe, die nicht bewertet werden können oder zu einer Endlosschleife führen würden (wie
1d1R1
oder3d6R[1, 2, 3, 4, 5, 6]
), sind ungültig
Testfälle
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
Alle außer dem letzten Testfall wurden mit der Referenzimplementierung generiert.
Gearbeitetes Beispiel
Ausdruck: 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]
(voll: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
(getan)
Referenzimplementierung
Diese Referenzimplementierung verwendet dieselbe Konstante seed ( 0
), um jeden Ausdruck auf testbare, konsistente Ausgaben zu untersuchen. Es werden Eingaben in STDIN erwartet, wobei die einzelnen Ausdrücke durch Zeilenumbrüche voneinander getrennt sind.
#!/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]: Unsere Definition adb
für Matrixargumente war, AdX
für jedes X
in a * b
, wo zu rollen A = det(a * b)
. Das ist natürlich zu absurd für diese Herausforderung.
-
dass diesb
immer weniger ist, alsa
ich sehe, gibt es keine Möglichkeit, nicht positive ganze Zahlen zu erhalten, daher scheint die zweite zusätzliche Regel sinnlos. OTOH,_
könnte zu einer leeren Liste führen, was in den gleichen Fällen nützlich erscheint. Was bedeutet es jedoch, wenn eine Ganzzahl benötigt wird? Normalerweise würde ich sagen, die Summe ist0
...0
. Nach der No-Non-Positives-Regel würde es als ausgewertet1
.[1,2]_([1]_[1])
ist[1,2]
?[2]
, weil[1]_[1] -> [] -> 0 -> 1 -> [1]
.Antworten:
Python 3,
803 788 753 749 744 748 745 740 700 695682 Bytes-5 Bytes dank Mr.Xcoder
-5 weitere Bytes dank NGN
-ungefähr 40 Bytes dank Jonathan French
Yuck, was für ein Trottel! Dies funktioniert, indem ich einen regulären Ausdruck verwende, um alle Zahlen in meiner
k
Klasse umzubrechen , und alle Operatoren in Operatoren konvertiere, die Python versteht, und dann die magischen Methoden derk
Klasse verwende, um die Mathematik zu handhaben. Das+-
und+--
am Ende für.
und_
sind ein Hack, um den Vorrang richtig zu halten. Ebenso kann ich den**
Operator für d nicht verwenden, da dies dazu führen würde,1d4d4
dass analysiert wird als1d(4d4)
. Stattdessen fasse ich alle Zahlen in einen zusätzlichen Satz von Parens ein und mache d as.j
, da Methodenaufrufe Vorrang vor Operatoren haben. Die letzte Zeile wird als anonyme Funktion ausgewertet, die den Ausdruck auswertet.quelle
def __mod__(a, b)
... Warum der Raum zwischena,
undb
?; __sub__
. Und möglicherweise auch hier:lambda a,b: l(
.exec("""...""".replace("...","..."))
Anweisung einschließen und häufig vorkommende Zeichenfolgenreturn
durch ein einzelnes Zeichen ersetzen . Allerdingsexec
scheint mir die Strategie immer ein bisschen unelegant ...__mod__
und__add__
brauchen nicht so vielAPL (Dyalog Classic) , 367 Byte
Probieren Sie es online!
Dies ist der Rangierbahnhof-Algorithmus aus der Referenzimplementierung, mit dem zusammengeführt wird
evaluate_dice()
, ohne den kruft- und objektorientierten Unsinn. Es werden nur zwei Stapel verwendet:h
für die Operatoren undv
für die Werte. Analyse und Auswertung sind verschachtelt.Zwischenergebnisse werden als 2 × N-Matrizen dargestellt, wobei die erste Reihe die Zufallswerte und die zweite Reihe die Anzahl der Seiten auf den Würfeln sind, die sie erzeugt haben. Wenn der Würfelwurfoperator "d" kein Ergebnis liefert, enthält die zweite Zeile beliebige Zahlen. Ein einzelner Zufallswert ist eine 2 × 1-Matrix und daher nicht von einer Liste mit 1 Element zu unterscheiden.
quelle
Python 3:
723722714711707675653665 BytesDer Einstiegspunkt ist
E
. Dies gilt iterativ für reguläre Ausdrücke. Zuerst werden alle Ganzzahlenx
durch ein Tupel mit einer Singleton-Liste ersetzt[(x,0)]
. Dann führt der erste reguläre Ausdruck died
Operation aus, indem alle[(x,0)]d[(b,0)]
durch die Zeichenfolgendarstellung eines Arrays von Tupeln wie ersetzt werden[(1,b),(2,b),(3,b)]
. Das zweite Element jedes Tupels ist der zweite Operand vond
. Anschließend führen nachfolgende reguläre Ausdrücke die anderen Operatoren aus. Es gibt einen speziellen regulären Ausdruck, um Parens aus vollständig berechneten Ausdrücken zu entfernen.quelle
Clojure,
731720 Bytes(wenn Zeilenumbrüche entfernt werden)
Update: eine kürzere Implementierung von
F
.Dies besteht aus vier Hauptteilen:
N
: zwingt eine Liste in eine Zahlg
: wertet einen abstrakten Syntaxbaum aus (S-Ausdrücke mit 3 Elementen)F
: Konvertiert ein AST-Infix in eine Präfixnotation (S-Ausdrücke). Wendet auch die Rangfolge der Operanden anf
:read-string
Konvertiert eine Zeichenfolge in eine verschachtelte Folge von Zahlen und Symbolen (Infix AST), leitet sie durch F -> g -> N und gibt die Ergebnisnummer zurück.Ich bin mir nicht sicher, wie ich das gründlich testen soll, vielleicht über statistische Tests gegen eine Referenzimplementierung? Zumindest der AST und seine Bewertung ist relativ einfach zu verfolgen.
Beispiel S-Ausdruck von
1d((8d20t4T2)d(6d6R1r6)-2d4+1d2).(1d(4d6_3d3))
:Weniger Golf mit Zwischenergebnissen und Tests:
quelle
Python 3, 695 Bytes
Ein Interpreter, der mit
tatsu
einer PEG-Parser-Bibliothek erstellt wurde. Das erste Argument dafürtatsu.parser()
ist die PEG-Grammatik.class D
(für Die) klassifiziert den eingebautenint
Typ. Ihr Wert ist das Ergebnis einer Rolle. Das Attribut.s
ist die Anzahl der Seiten auf dem Würfel.class S
hat die semantischen Aktionen für den Parser und implementiert den Interpreter.quelle