Lý lịch
Tôi chơi D & D thường xuyên với một số người bạn. Trong khi nói về sự phức tạp của một số hệ thống / phiên bản khi nói đến việc gieo xúc xắc và áp dụng các phần thưởng và hình phạt, chúng tôi đã đùa rằng đã đưa ra một số phức tạp bổ sung cho các biểu thức lăn xúc xắc. Một số trong số chúng quá thái quá (như mở rộng các biểu thức xúc xắc đơn giản như 2d6
đối số ma trận 1 ), nhưng phần còn lại tạo nên một hệ thống thú vị.
Các thách thức
Đưa ra một biểu thức xúc xắc phức tạp, đánh giá nó theo các quy tắc sau và đưa ra kết quả.
Quy tắc đánh giá cơ bản
- Bất cứ khi nào một toán tử mong đợi một số nguyên nhưng nhận được một danh sách cho toán hạng, tổng của danh sách đó được sử dụng
- Bất cứ khi nào một toán tử mong đợi một danh sách nhưng nhận được một số nguyên cho toán hạng, số nguyên được coi là một danh sách một phần tử có chứa số nguyên đó
Người vận hành
Tất cả các toán tử là toán tử infix nhị phân. Với mục đích giải thích, a
sẽ là toán hạng bên trái và b
sẽ là toán hạng bên phải. Ký hiệu danh sách sẽ được sử dụng cho các ví dụ trong đó các toán tử có thể lấy danh sách làm toán hạng, nhưng các biểu thức thực tế chỉ bao gồm các số nguyên và toán tử dương.
d
: đầu raa
số nguyên ngẫu nhiên thống nhất độc lập trong phạm vi[1, b]
- Ưu tiên: 3
- Cả hai toán hạng đều là số nguyên
- Ví dụ:
3d4 => [1, 4, 3]
,[1, 2]d6 => [3, 2, 6]
t
: lấy cácb
giá trị thấp nhất từa
- Ưu tiên: 2
a
là một danh sách,b
là một số nguyên- Nếu
b > len(a)
, tất cả các giá trị được trả về - Ví dụ:
[1, 5, 7]t1 => [1]
,[5, 18, 3, 9]t2 => [3, 5]
,3t5 => [3]
T
: lấy cácb
giá trị cao nhất từa
- Ưu tiên: 2
a
là một danh sách,b
là một số nguyên- Nếu
b > len(a)
, tất cả các giá trị được trả về - Ví dụ:
[1, 5, 7]T1 => [7]
,[5, 18, 3, 9]T2 => [18, 9]
,3T5 => [3]
r
: nếu có bất kỳ phần tửb
nào tronga
, hãy chạy lại các phần tử đó, sử dụng bất kỳd
câu lệnh nào đã tạo ra chúng- Ưu tiên: 2
- Cả hai toán hạng đều là danh sách
- Việc ghi lại chỉ được thực hiện một lần, do đó có thể vẫn có các yếu tố
b
trong kết quả - Ví dụ:
3d6r1 => [1, 3, 4] => [6, 3, 4]
,2d4r2 => [2, 2] => [3, 2]
,3d8r[1,8] => [1, 8, 4] => [2, 2, 4]
R
: nếu có bất kỳ phần tử nào ởb
tronga
, hãy lặp lại các phần tử đó cho đến khi không có phần tửb
nào xuất hiện, sử dụng bất kỳd
câu lệnh nào tạo ra chúng- Ưu tiên: 2
- Cả hai toán hạng đều là danh sách
- Ví dụ:
3d6R1 => [1, 3, 4] => [6, 3, 4]
,2d4R2 => [2, 2] => [3, 2] => [3, 1]
,3d8R[1,8] => [1, 8, 4] => [2, 2, 4]
+
: thêma
vàb
cùng nhau- Ưu tiên: 1
- Cả hai toán hạng đều là số nguyên
- Ví dụ:
2+2 => 4
,[2]+[2] => 4
,[3, 1]+2 => 6
-
: Trừb
từa
- Ưu tiên: 1
- Cả hai toán hạng đều là số nguyên
b
sẽ luôn luôn ít hơna
- Ví dụ:
2-1 => 1
,5-[2] => 3
,[8, 3]-1 => 10
.
: nốia
vàb
cùng nhau- Ưu tiên: 1
- Cả hai toán hạng đều là danh sách
- Ví dụ:
2.2 => [2, 2]
,[1].[2] => [1, 2]
,3.[4] => [3, 4]
_
: đầu raa
với tất cả các yếu tốb
bị loại bỏ- Ưu tiên: 1
- Cả hai toán hạng đều là danh sách
- Ví dụ:
[3, 4]_[3] => [4]
,[2, 3, 3]_3 => [2]
,1_2 => [1]
Quy tắc bổ sung
- Nếu giá trị cuối cùng của biểu thức là một danh sách, nó được tính tổng trước khi xuất
- Việc đánh giá các thuật ngữ sẽ chỉ dẫn đến các số nguyên dương hoặc danh sách các số nguyên dương - bất kỳ biểu thức nào dẫn đến một số nguyên không dương hoặc một danh sách chứa ít nhất một số nguyên không dương sẽ có các giá trị đó được thay thế bằng
1
s - Dấu ngoặc đơn có thể được sử dụng để nhóm các thuật ngữ và chỉ định thứ tự đánh giá
- Các toán tử được đánh giá theo thứ tự ưu tiên cao nhất đến ưu tiên thấp nhất, với việc tiến hành đánh giá từ trái sang phải trong trường hợp ưu tiên ràng buộc (vì vậy
1d4d4
sẽ được đánh giá là(1d4)d4
) - Thứ tự của các phần tử trong danh sách không thành vấn đề - hoàn toàn chấp nhận được đối với toán tử sửa đổi danh sách để trả về nó với các phần tử theo thứ tự tương đối khác
- Các thuật ngữ không thể được đánh giá hoặc sẽ dẫn đến một vòng lặp vô hạn (như
1d1R1
hoặc3d6R[1, 2, 3, 4, 5, 6]
) không hợp lệ
Các trường hợp thử nghiệm
Định dạng: 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
Tất cả trừ trường hợp thử nghiệm cuối cùng được tạo ra với việc thực hiện tham chiếu.
Ví dụ làm việc
Biểu hiện: 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]
(đầy đủ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
(làm xong)
Thực hiện tham khảo
Việc triển khai tham chiếu này sử dụng cùng một hạt giống hằng ( 0
) để đánh giá từng biểu thức cho các đầu ra nhất quán, có thể kiểm tra được. Nó mong đợi đầu vào trên STDIN, với các dòng mới phân tách từng biểu thức.
#!/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]: Định nghĩa của chúng tôi về các adb
đối số ma trận là cuộn AdX
cho từng X
trong a * b
, ở đâu A = det(a * b)
. Rõ ràng đó là quá vô lý cho thử thách này.
-
điều đó b
sẽ luôn luôn ít hơn a
tôi thấy không có cách nào để có được số nguyên không tích cực, vì vậy quy tắc bổ sung thứ hai dường như vô nghĩa. OTOH, _
có thể dẫn đến một danh sách trống, có vẻ hữu ích trong các trường hợp tương tự nhưng điều đó có nghĩa là gì khi cần một số nguyên? Thông thường tôi sẽ nói tổng là 0
...
0
. Theo quy tắc không tích cực, nó sẽ được đánh giá là a 1
.
[1,2]_([1]_[1])
là [1,2]
sao?
[2]
, bởi vì [1]_[1] -> [] -> 0 -> 1 -> [1]
.