Задний план
Я регулярно играю в D & D с друзьями. Говоря о сложности некоторых систем / версий, когда речь идет о броске кубиков и применении бонусов и штрафов, мы в шутку придумали некоторую дополнительную сложность для выражений броска кубиков. Некоторые из них были слишком возмутительными (например, расширение простых выражений в виде костей, как 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
.
: concateatea
и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]
Дополнительные правила
- Если конечное значение выражения является списком, оно суммируется перед выводом
- Оценка терминов приведет только к положительным целым числам или спискам положительных целых чисел - любое выражение, которое приводит к неположительному целому числу или списку, содержащему хотя бы одно неположительное целое число, будет заменять эти значения на
1
s - Скобки можно использовать для группировки терминов и определения порядка оценки
- Операторы оцениваются в порядке наивысшего приоритета и наименьшего приоритета, причем оценка выполняется слева направо в случае связанного приоритета (поэтому
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
(сделанный)
Реализация эталона
Эта эталонная реализация использует одну и ту же константу seed ( 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
in a * b
, где A = det(a * b)
. Очевидно, что это слишком абсурдно для этого вызова.
-
этоb
всегда будет меньше, чемa
я не вижу способа получить неположительные целые числа, поэтому второе дополнительное правило кажется бессмысленным. OTOH,_
может привести к пустому списку, который кажется полезным в тех же случаях, но что это значит, когда необходимо целое число? Обычно я бы сказал, что сумма0
...0
. По правилу «нет не положительных результатов» оно будет оцениваться как1
.[1,2]_([1]_[1])
есть[1,2]
?[2]
, потому что[1]_[1] -> [] -> 0 -> 1 -> [1]
.Ответы:
Python 3,
803 788 753 749 744 748 745 740 700 695682 байта-5 байт благодаря Mr.Xcoder
-5 больше байтов благодаря NGN
- около 40 байтов благодаря Джонатану Френчу
Фу, какой клудж! Это работает с помощью регулярного выражения, чтобы обернуть все числа в моем
k
классе, и преобразование всех операторов в операторы, понятные Python, а затем использование магических методовk
класса для обработки математических операций .+-
И+--
в конце для.
и_
являются хак , чтобы сохранить преимущество правильно. Аналогично, я не могу использовать**
оператор для d, потому что при этом будет1d4d4
проанализировано как1d(4d4)
. Вместо этого я обертываю все числа в дополнительный набор символов и делаю d as.j
, поскольку вызовы методов имеют более высокий приоритет, чем операторы. Последняя строка оценивается как анонимная функция, которая оценивает выражение.источник
def __mod__(a, b)
... Почему пространство междуa,
иb
?; __sub__
. И , возможно , также здесь:lambda a,b: l(
.exec("""...""".replace("...","..."))
оператор и заменив строки (которые часто встречаются (напримерreturn
)) одним символом. Тем не менее, мнеexec
стратегия всегда кажется немного нелегкой ...__mod__
и__add__
не нужно , что много отступаAPL (Dyalog Classic) , 367 байтов
Попробуйте онлайн!
Это алгоритм маневрового двора из эталонной реализации, слитый
evaluate_dice()
без лишних и объектно-ориентированных глупостей. Используются только два стека:h
для операторов иv
для значений. Разбор и оценка чередуются.Промежуточные результаты представлены в виде матриц 2 × N, где первая строка - это случайные значения, а вторая строка - количество сторон на кости, которая их произвела. Когда результат не выдается оператором «d», бросающим кости, вторая строка содержит произвольные числа. Одно случайное значение представляет собой матрицу 2 × 1 и, таким образом, неотличимо от списка из 1 элемента.
источник
Python 3:
723722714711707675653665 байтТочка входа есть
E
. Это применяет регулярные выражения итеративно. Сначала он заменяет все целые числаx
набором одноэлементного списка[(x,0)]
. Затем первое регулярное выражение выполняетd
операцию, заменяя все[(x,0)]d[(b,0)]
строковым представлением массива кортежей типа[(1,b),(2,b),(3,b)]
. Второй элемент каждого кортежа является вторым операндомd
. Затем последующие регулярные выражения выполняют каждый из остальных операторов. Существует специальное регулярное выражение для удаления паренов из полностью вычисляемых выражений.источник
Clojure,
731720 байт(когда удаляются новые строки)
Обновление: более короткая реализация
F
.Он состоит из четырех основных частей:
N
: приводит список в числоg
: оценивает абстрактное синтаксическое дерево (S-выражения с 3 элементами)F
: преобразует инфиксный AST в префиксную нотацию (S-выражения), также применяет приоритет операндаf
: используетread-string
для преобразования строки во вложенную последовательность чисел и символов (инфикс AST), передает их через F -> g -> N, возвращая номер результата.Я не уверен, как тщательно это проверить, может быть, с помощью статистических тестов с эталонной реализацией? По крайней мере, AST и его оценка относительно просты для отслеживания.
Пример S-выражения из
1d((8d20t4T2)d(6d6R1r6)-2d4+1d2).(1d(4d6_3d3))
:Меньше игры в гольф с промежуточными результатами и тестами:
источник
Python 3, 695 байт
Интерпретатор построен с использованием
tatsu
библиотеки синтаксического анализатора PEG. Первым аргументомtatsu.parser()
является грамматика PEG.class D
(для Die) подклассы встроенногоint
типа. Это значение является результатом броска. Атрибутом.s
является количество сторон на кристалле.class S
имеет семантические действия для синтаксического анализатора и реализует интерпретатор.источник