배경
나는 친구들과 정기적으로 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]
추가 규칙
- 식의 최종 값이 목록이면 출력 전에 합산됩니다.
- 항을 평가하면 양의 정수 또는 양의 정수 목록 만 나타납니다. 양수가 아닌 정수 또는 하나 이상의 양수가 아닌 정수를 포함하는 목록의 결과는
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
(끝난)
참조 구현
이 참조 구현은 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 * b
A = det(a * b)
-
그 b
보다 항상 적을 것이다 a
번째 추가 규칙은 무의미한 것 같다, 그래서 내가 아닌 양의 정수를 얻을 수있는 방법을 볼 수 없습니다. OTOH, _
빈 목록이 생길 수 있습니다. 같은 경우에 유용하지만 정수가 필요할 때의 의미는 무엇입니까? 일반적으로 나는 합계가 0
... 라고 말하고 싶습니다
0
. 양성이 아닌 규칙에 따라로 평가됩니다 1
.
[1,2]_([1]_[1])
이다 [1,2]
?
[2]
하기 때문에 [1]_[1] -> [] -> 0 -> 1 -> [1]
.