Фон
Я регулярно граю в науково-дослідні роботи з деякими друзями. Говорячи про складність деяких систем / версій, коли справа доходить до кочення кісток і застосування бонусів та штрафних санкцій, ми жартома придумали додаткову складність для виразів прокатки кісток. Деякі з них були занадто обурливими (як розширення простих виразів з кістки, як 2d6аргументи матриці 1 ), а решта створюють цікаву систему.
Змагання
Давши складний вираз кубиків, оцініть його за наступними правилами та виведіть результат.
Основні правила оцінювання
- Щоразу, коли оператор очікує ціле число, але отримує список для операнду, використовується сума цього списку
- Кожного разу, коли оператор очікує список, але отримує ціле число для операнда, ціле число розглядається як список одного елемента, що містить це ціле число
Оператори
Усі оператори є операторами бінарної інфіксації. З метою пояснення aбуде лівий операнд, а bправий операнд. Позначення списків використовуватимуться для прикладів, коли оператори можуть приймати списки як операнди, але фактичні вирази складаються лише з натуральних чисел та операторів.
d: вивестиaнезалежні рівномірні випадкові цілі числа в діапазоні[1, b]- Перевага: 3
- Обидва операнди є цілими числами
- Приклади:
3d4 => [1, 4, 3],[1, 2]d6 => [3, 2, 6]
t: прийнятиbнайнижчі значення відa- Пріоритетність: 2
aє списком,bє цілим числом- Якщо
b > len(a)всі значення повертаються - Приклади:
[1, 5, 7]t1 => [1],[5, 18, 3, 9]t2 => [3, 5],3t5 => [3]
T: приймайтеbнайвищі значення уa- Пріоритетність: 2
aє списком,bє цілим числом- Якщо
b > len(a)всі значення повертаються - Приклади:
[1, 5, 7]T1 => [7],[5, 18, 3, 9]T2 => [18, 9],3T5 => [3]
r: Якщо якісь - або елементиbвa, перекинути ці елементи, використовуючи будь-якийdзаяву генеруватися їх- Пріоритетність: 2
- Обидва операнди - це списки
- Перепродаж проводиться лише один раз, тому
bв результаті все одно можуть бути елементи - Приклади:
3d6r1 => [1, 3, 4] => [6, 3, 4],2d4r2 => [2, 2] => [3, 2],3d8r[1,8] => [1, 8, 4] => [2, 2, 4]
R: Якщо якісь - або елементиbвa, перекинути ці елементи , поки ніяких елементівbнемає, використовуючи будь-якийdзаяву генеруватися їх- Пріоритетність: 2
- Обидва операнди - це списки
- Приклади:
3d6R1 => [1, 3, 4] => [6, 3, 4],2d4R2 => [2, 2] => [3, 2] => [3, 1],3d8R[1,8] => [1, 8, 4] => [2, 2, 4]
+: додатиaіbразом- Пріоритетність: 1
- Обидва операнди є цілими числами
- Приклади:
2+2 => 4,[2]+[2] => 4,[3, 1]+2 => 6
-: віднятиbвідa- Пріоритетність: 1
- Обидва операнди є цілими числами
bзавжди буде менше, ніжa- Приклади:
2-1 => 1,5-[2] => 3,[8, 3]-1 => 10
.: з'єднатиaіbразом- Пріоритетність: 1
- Обидва операнди - це списки
- Приклади:
2.2 => [2, 2],[1].[2] => [1, 2],3.[4] => [3, 4]
_: вихідaз усіма елементамиbвидаленими- Пріоритетність: 1
- Обидва операнди - це списки
- Приклади:
[3, 4]_[3] => [4],[2, 3, 3]_3 => [2],1_2 => [1]
Додаткові правила
- Якщо кінцеве значення виразу є списком, воно підсумовується перед виведенням
- Оцінка термінів призведе лише до позитивних цілих чисел або списків натуральних чисел - будь-яке вираження, що призводить до непозитивного цілого чи списку, що містить принаймні одне невід’ємне ціле число, замінить на значення
1s - Дужки можна використовувати для групування термінів та вказівки порядку оцінки
- Оператори оцінюються в порядку найвищого до найнижчого пріоритету, при цьому оцінка проводиться зліва направо у випадку прив'язки пріоритету (так
1d4d4би оцінювалося як(1d4)d4) - Порядок елементів у списках не має значення - цілком прийнятно для оператора, який модифікує список, повертати його зі своїми елементами в іншому відносному порядку
- Терміни, які неможливо оцінити або призведе до нескінченного циклу (наприклад,
1d1R1або3d6R[1, 2, 3, 4, 5, 6]), не є дійсними
Випробування
Формат: 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
Усі, крім останнього тестового випадку, були створені при здійсненні посилання.
Приклад роботи
Вираз: 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](повна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(зроблено)
Довідкова реалізація
Ця контрольна реалізація використовує те саме постійне насіння ( 0) для оцінки кожного виразу для перевірених, послідовних результатів. Він очікує введення на STDIN, причому нові виразки розділяють кожен вираз.
#!/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]: Нашим визначенням adbдля аргументів матриці було прокручування AdXкожного Xв a * b, де A = det(a * b). Очевидно, що це занадто абсурдно для цього виклику.
-це bзавжди буде меншою, ніж aя не бачу способу отримати непозитивні цілі числа, тому друге додаткове правило здається безглуздим. OTOH _може призвести до порожнього списку, який здається корисним у тих же випадках, але що це означає, коли потрібне ціле число? Зазвичай я б сказав, що сума 0...
0. За правилом непозитивності, це оцінюється як a 1.
[1,2]_([1]_[1])є [1,2]?
[2], що [1]_[1] -> [] -> 0 -> 1 -> [1].