배경
나는 친구들과 정기적으로 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
.: 연결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행렬 인수 에 대한 정의는 , where 에서 AdX각각 X에 대해 롤 하는 것이 었습니다 . 분명히이 도전에는 너무 터무니 없습니다.a * bA = det(a * b)
-그 b보다 항상 적을 것이다 a번째 추가 규칙은 무의미한 것 같다, 그래서 내가 아닌 양의 정수를 얻을 수있는 방법을 볼 수 없습니다. OTOH, _빈 목록이 생길 수 있습니다. 같은 경우에 유용하지만 정수가 필요할 때의 의미는 무엇입니까? 일반적으로 나는 합계가 0... 라고 말하고 싶습니다
0. 양성이 아닌 규칙에 따라로 평가됩니다 1.
[1,2]_([1]_[1])이다 [1,2]?
[2]하기 때문에 [1]_[1] -> [] -> 0 -> 1 -> [1].