复杂骰子滚动表达式


23

背景

我定期和一些朋友一起玩D&D。在谈论某些系统/版本在掷骰子时的复杂性以及应用奖金和罚金时,我们开玩笑地为骰子掷骰表达了一些额外的复杂性。它们中的一些太离谱了(例如扩展简单的dice表达式,例如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:如果中有任何元素,ba使用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]
  • +:加ab在一起
    • 优先顺序:1
    • 两个操作数都是整数
    • 例如:2+2 => 4[2]+[2] => 4[3, 1]+2 => 6
  • -:减去ba
    • 优先顺序:1
    • 两个操作数都是整数
    • b 永远小于 a
    • 例如:2-1 => 15-[2] => 3[8, 3]-1 => 10
  • .:串连ab在一起
    • 优先顺序:1
    • 两个操作数都是列表
    • 例如:2.2 => [2, 2][1].[2] => [1, 2]3.[4] => [3, 4]
  • _:输出ab删除的 所有元素
    • 优先顺序:1
    • 两个操作数都是列表
    • 例如:[3, 4]_[3] => [4][2, 3, 3]_3 => [2]1_2 => [1]

附加规则

  • 如果表达式的最终值是列表,则将其求和后再输出
  • 项的求值将仅产生正整数或正整数列表-任何导致非正整数或包含至少一个非正整数的列表的表达式都将用1s 替换那些值
  • 括号可用于对术语进行分组并指定评估顺序
  • 运算符将按照从最高优先级到最低优先级的顺序进行评估,如果是捆绑优先级,则评估从左到右进行(因此1d4d4将评估为(1d4)d4
  • 列表中元素的顺序无关紧要-修改列表的操作员以不同的相对顺序返回带有元素的列表是完全可以接受的
  • 无法评估或可能导致无限循环的字词(如1d1R13d6R[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))

  1. 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))
  2. 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))
  3. [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))
  4. 2d4 => 71d([1, 6, 11, 7, 2, 8, 15, 3, 4, 18, 11, 11, 1, 10, 8, 6, 11]-7+1d2).(1d(4d6_3d3))
  5. 1d2 => 21d([1, 6, 11, 7, 2, 8, 15, 3, 4, 18, 11, 11, 1, 10, 8, 6, 11]-7+2).(1d(4d6_3d3))
  6. [1, 6, 11, 7, 2, 8, 15, 3, 4, 18, 11, 11, 1, 10, 8, 6, 11]-7+2 => 133-7+2 => 1281d128).(1d(4d6_3d3))
  7. 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])
  8. 1d[1, 3, 3, 6, 3, 2, 2] => 1d20 => 61d128).(6)
  9. 1d128 => 5555.6
  10. 55.6 => [55, 6][55, 6]
  11. [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对每个Xa * b其中A = det(a * b)。显然,对于这个挑战来说太荒谬了。



有了这样的保证-,我b将永远无法a获得非正整数,因此第二条附加规则似乎毫无意义。OTOH _可能会导致列表为空,这在相同情况下似乎很有用,但是当需要整数时又意味着什么呢?通常我会说总和是0……
Christian Sievers

@ChristianSievers 1)为了清楚起见,我添加了有关非正整数的附加说明。2)空列表的总和为0。根据非非正则规则,它将被评估为1
Mego

可以,但是作为中间结果可以吗?所以[1,2]_([1]_[1])[1,2]
Christian Sievers

@ChristianSievers不。这将导致[2],因为[1]_[1] -> [] -> 0 -> 1 -> [1]
Mego

Answers:


9

Python 3中,803 788 753 749 744 748个745 740 700 695 682字节

exec(r'''from random import*
import re
class k(int):
 p=0;j=Xl([d(randint(1,int(b)),b)Zrange(a)]);__mul__=Xl(sorted(Y)[:b]);__matmul__=Xl(sorted(Y)[-b:]);__truediv__=Xl([d(randint(1,int(_.i)),_.i)if _==b else _ ZY]);__sub__=Xk(max(1,int.__sub__(a,b)))
 def __mod__(a,b):
  x=[]
  while x!=Y:x=Y;a=a/b
  Wl(x)
 def V:
  if b.p:p=b.p;del b.p;Wl(Y+b.l)if~-p else l([_ZY if~-(_ in b.l)])
  Wk(int.V)
 def __neg__(a):a.p+=1;Wa
def l(x):a=k(sum(x)or 1);Y=x;Wa
def d(v,i):d=k(v);d.i=i;Wd
lambda a:eval(re.sub("(\d+)","(k(\\1))",a).translate({100:".j",116:"*",84:"@",114:"/",82:"%",46:"+--",95:"+-"}))'''.translate({90:" for _ in ",89:"a.l",88:"lambda a,b:",87:"return ",86:"__add__(a,b)"}))

-5字节归功于Mr.Xcoder

-NGN多了-5个字节

-约40个字节,感谢Jonathan French

uck,太烂了!通过使用正则表达式将所有数字包装在我的k类中,然后将所有运算符转换为python理解的运算符,然后使用k该类的魔术方法来处理数学,可以实现这一目的。该+-+--在年底._是保持正确的优先黑客攻击。同样,我不能将**运算符用于d,因为这样做会使1d4d4之解析为1d(4d4)。取而代之的是,我将所有数字包装在一组额外的括号中,并执行d as .j,因为方法调用的优先级高于运算符。最后一行评估为评估表达式的匿名函数。


def __mod__(a, b)...为什么之间的空间a,b
Xcoder先生17年


@ Mr.Xcoder我认为您可以通过删除不必要的空间来节省一个字节:; __sub__。也可能在这里:lambda a,b: l(
乔纳森·弗雷希

1
您可以通过将整个代码包装在一条exec("""...""".replace("...","..."))语句中并用return 单个字符替换经常出现的字符串(如)来节省一些字节。然而,对我而言,exec战略似乎总是有些昧...
Jonathan Frech

的尸体__mod____add__不需要太多缩进
NGN

3

APL(Dyalog Classic),367字节

d←{(⊢⍪⍨1+?)⍉⍪⊃⍴/⊃¨+/¨⍺⍵}⋄r←{z←⊣⌿⍺⋄((m×?n)+z×~m←z∊⊣⌿⍵)⍪n←⊢⌿⍺}⋄R←{⍬≡⊃∩/⊣⌿¨⍺⍵:⍺⋄⍵∇⍨⍺r⍵}
u←{⍺[;((⊃+/⍵)⌊≢⍉⍺)↑⍺⍺⊣⌿⍺]}⋄t←⍋u⋄T←⍒u⋄A←+/,⋄S←+/,∘-⋄C←,⋄D←{⍺/⍨~⊃∊/⊣⌿¨⍺⍵}
hv←⍬⋄o'drRtT+-._'f←{8<io⍳⊃⍵:0v⊢←(¯2v),(⍎i'drRtTASCD')/¯2v}
{⊃⍵∊⎕d:v,←⊂⍪2↑⍎⍵⋄'('=⍵:h,←⍵⋄')'=⍵:h↑⍨←i-1f¨⌽h↓⍨i←+/∨\⌽h='('⋄h,←⍵⊣h↓⍨←-i⊣f¨⌽h↑⍨-i←+/\⌽≤/(1 4 4 1/⌽⍳4)[o⍳↑⍵,¨h]}¨'\d+' '.'s'&'⊢⍞
f¨⌽h1⌈+/⊣⌿⊃v

在线尝试!

这是参考实现中与合并的调车场算法evaluate_dice(),没有任何混乱和面向对象的废话。仅使用两个堆栈:h用于运算符和v用于值。解析和评估是交错的。

中间结果表示为2×N矩阵,其中第一行是随机值,第二行是产生它们的骰子的边数。当掷骰子“ d”运算符未产生结果时,第二行包含任意数字。单个随机值是2×1矩阵,因此无法与1元素列表区分开。


3

Python 3中:723 722 714 711 707 675 653 665字节

import re
from random import*
S=re.subn
e=eval
r=randint
s=lambda a:sum(g(e(a)))or 1
g=lambda a:next(zip(*a))
def o(m):a,o,b=m.groups();A=sorted(e(a));t=g(e(b));return str(o in"rR"and([([v,(r(1,d)*(o>"R")or choice([n+1for n in range(d)if~-(n+1in t)]))][v in t],d)for(v,d)in A])or{"t":A[:s(b)],"T":A[-s(b):],"+":[(s(a)+s(b),0)],"-":[(s(a)-s(b),0)],".":e(a)+e(b),"_":[t for t in e(a)if~-(t[0]in g(e(b)))]}[o])
def d(m):a,b=map(s,m.groups());return str([(r(1,b),b)for _ in" "*a])
p=r"(\[[^]]+\])"
def E(D):
 D,n=S(r"(\d+)",r"[(\1,0)]",D)
 while n:
  n=0
  for e in[("\(("+p+")\)",r"\1"),(p+"d"+p,d),(p+"([tTrR])"+p,o),(p+"(.)"+p,o)]:
   if n<1:D,n=S(*e,D)
 return s(D)

入口点是E。这将迭代地应用正则表达式。首先,它用x单例列表元组替换所有整数[(x,0)]。然后,第一个正则表达式d通过将所有内容替换[(x,0)]d[(b,0)]为元组数组的字符串表示形式(如)来执行操作[(1,b),(2,b),(3,b)]。每个元组的第二个元素是的第二个操作数d。然后,后续的正​​则表达式将执行其他每个运算符。有一个特殊的正则表达式可从完全计算的表达式中删除括号。


3

Clojure, 731720字节

(删除换行符时)

更新:的较短实现F

(defn N[i](if(seq? i)(apply + i)i))
(defn g[i](let[L(fn[i](let[v(g i)](if(seq? v)v(list v))))R remove T take](if(seq? i)(let[[o a b :as A]i](if(some symbol? A)(case o d(repeatedly(N(g a))(fn[](inc(rand-int(N(g b))))))t(T(N(g b))(sort(g a)))T(T(N(g b))(sort-by -(g a)))r(for[i(L a)](if((set(L b))i)(nth(L a)0)i))R(T(count(L a))(R(set(L b))(for[_(range)i(L a)]i)))+(+(N(g a))(N(g b)))-(-(N(g a))(N(g b))).(into(L a)(L b))_(R(set(L b))(g a)))A))i)))
(defn F[T](if(seq? T)(if(next T)(loop[[s & S]'[_ . - + R r T t d]](let[R reverse[b a](map R(split-with(comp not #{s})(R T)))a(butlast a)](if a(cons s(map F[a b]))(recur S))))(F(first T)))T))
(defn f[i](N(g(F(read-string(clojure.string/replace(str"("i")")#"[^0-9]"" $0 "))))))

这包括四个主要部分:

  • N:将列表强制转换为数字
  • g:评估一个抽象语法树(带有3个项目的S表达式)
  • F:将中缀AST转换为前缀表示法(S表达式),也应用操作数顺序优先级
  • f:用于read-string将字符串转换为嵌套的数字和符号序列(后缀AST),将其通过F-> g-> N进行管道传输,并返回结果数字。

我不确定如何通过参考测试的统计测试来对此进行全面测试?至少AST及其评估相对容易遵循。

来自的示例S表达式1d((8d20t4T2)d(6d6R1r6)-2d4+1d2).(1d(4d6_3d3))

(. (d 1 (- (d (T (t (d 8 20) 4) 2)
              (R (d 6 6) (r 1 6)))
           (+ (d 2 4)
              (d 1 2))))
   (d 1 (_ (d 4 6) (d 3 3))))

具有中等结果和测试的高尔夫较少:

(def f #(read-string(clojure.string/replace(str"("%")")#"[^0-9]"" $0 ")))

(defn F [T]
  (println {:T T})
  (cond
    (not(seq? T))T
    (symbol?(first T))T
    (=(count T)1)(F(first T))
    1(loop [[s & S] '[_ . - + R r T t d]]
      (let[[b a](map reverse(split-with(comp not #{s})(reverse T)))
           _ (println [s a b])
           a(butlast a)]
        (cond
          a(do(println {:s s :a a :b b})(cons s(map F[a b])))
          S(recur S))))))


(->> "3d6" f F)
(->> "3d6t2" f F)
(->> "3d2R1" f F)
(->> "1d4d4" f F)
(->> "2d6.2d6" f F)
(->> "(3d2R1)d2" f F)
(->> "1d((8d20t4T2)d(6d6R1r6)-2d4+1d2).(1d(4d6_3d3))" f F)

(defn N[i](if(seq? i)(apply + i)i))

(defn g[i]
  (let[L(fn[i](let[v(g i)](if(seq? v)v(list v))))]
    (if(seq? i)
      (let[[o a b :as A] i]
        (println {:o o :a a :b b :all A})
        (if(every? number? A)(do(println{:A A})A)
           (case o
            d (repeatedly(N (g a))(fn[](inc(rand-int(N (g b))))))
            t (take(N (g b))(sort(g a)))
            T (take(N (g b))(sort-by -(g a)))
            r (for[i(L a)](if((set(L b))i)(nth(L a)0)i))
            R (take(count(g a))(remove(set(L b))(for[_(range)i(g a)]i)))
            + (+(N (g a))(N (g b)))
            - (-(N (g a))(N (g b)))
            . (into(L a)(L b))
            _ (remove(set(L b))(g a)))))
      (do(println {:i i})i))))


(g '(. (d 3 5) (d 4 3)))
(g '(. 1 (2 3)))
(g '(+ 1 (2 3)))
(g '(R (d 10 5) (d 1 3)))
(g '(T (d 5 20) 3))
(g '(t (d 5 20) 3))
(g '(d (d 3 4) 10))
(g '(d 4 3))
(g '(_ (d 4 6) (d 3 3)))

(->> "1d(4d6_3d3)" f F g)
(->> "1r6" f F g)
(->> "(8d20t4T2)d(6d6R1r6)" f F g)
(->> "(8d20t4T2)d(6d6R1r6)-2d4+1d2).(1d(4d6_3d3)" f F g)
(->> "1d((8d20t4T2)d(6d6R1r6)-2d4+1d2).(1d(4d6_3d3))" f F g))

2

Python 3,695字节

import random,tatsu
A=lambda x:sum(x)or 1
H=lambda x:[[d,D(d.s)][d in x[2]]for d in x[0]]
R=lambda x:R([H(x)]+x[1:])if{*x[0]}&{*x[2]}else x[0]
class D(int):
 def __new__(cls,s):o=super().__new__(cls,random.randint(1,s));o.s = s;return o
class S:
 o=lambda s,x:{'+':[A(x[0])+A(x[2])],'-':[A(x[0])-A(x[2])],'.':x[0]+x[2],'_':[d for d in x[0]if d not in x[2]]}[x[1]]
 f=lambda s,x:{'r':H(x),'R':R(x),'t':sorted(x[0])[:A(x[2])],'T':sorted(x[0])[-A(x[2]):]}[x[1]]
 d=lambda s,x:[D(A(x[2]))for _ in' '*A(x[0])]
 n=lambda s,x:[int(x)]
 l=lambda s,x:sum(x,[])
lambda i:tatsu.parse("s=e$;e=o|t;o=e/[-+._]/t;t=f|r;f=t/[rRtT]/r;r=d|a;d=r/d/a;a=n|l|p;n=/\d+/;l='['@:','.{n}']';p='('@:e')';",i,semantics=S())

使用tatsuPEG分析器库构建的解释器。的第一个参数tatsu.parser()是PEG语法。

class D(对于Die)子类化内置int类型。它的价值是滚动的结果。该属性.s是模具上的面数。

class S 具有解析器的语义动作,并实现解释器。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.