模拟爆炸骰子


31

您的任务是制作一个接受整数的程序n > 1,并输出单面n模具的卷。但是,此骰子遵循爆炸骰子的规则。

掷骰子时,请检查掷出的值。如果您获得了该类型骰子的最大值(在标准d4上为4,或者在d6上为6,依此类推),请再次滚动并将新的滚动添加到该总数中。每次滚动都会继续增加总数,直到您不再滚动最大数量为止。虽然最后一个数字仍然被添加。

您的程序应采用单个整数n,并滚动爆炸边的n模具。这是一个示例分布,以显示其外观n=4请注意,您永远不要输出的任何倍数n,因为它们总是会爆炸。

您可以假定您执行的任何递归的堆栈大小都是无限的,并且您的随机函数必须符合我们的随机性标准(内置随机生成器或time / date)。与诸如几何分布之类的东西相比,您的随机函数也应尽可能统一,因为我们正在谈论这些骰子。


1
该程序必须完美吗?像它的分布可能会减少极少吗?
Maltysen

致:Riker;RE:@Maltysen在上面的评论;或数量极高?
Artemis

Answers:


36

x86机器代码(用于Intel Ivy Bridge和更高版本),17个字节

31 C9 0F C7 F0 31 D2 F7 F6 42 01 D1 39 F2 74 F2 C3

上面的代码字节定义了一个模拟爆炸模具的函数。它需要一个输入,并传递到ESI寄存器中,指示管芯的最大数量。它在ECX寄存器中返回单个值,这是滚动的结果。

在内部,它使用RDRAND指令生成一个随机数。它使用随机数发生器(RNG),该发生器内置于Intel Ivy Bridge处理器及更高版本(某些AMD CPU也支持此指令)的硬件中。

否则,该功能的逻辑非常简单。使用标准技术((rand % dieSize) + 1)将生成的随机数缩放到所需范围内,然后检查是否会引起爆炸。最终结果保存在累加器寄存器中。

这是带注释的版本,显示了汇编语言助记符:

           unsigned int RollExplodingDie(unsigned int dieSize)
31 C9        xor     ecx, ecx    ; zero-out ECX, which accumulates the final result
           Roll:
0F C7 F0     rdrand  eax         ; generate a random number in EAX
31 D2        xor     edx, edx    ; zero-out EDX (in preparation for unsigned division)
F7 F6        div     esi         ; divide EDX:EAX by ESI (the die size)
                                 ;   EAX receives the quotient; EDX receives the remainder
42           inc     edx         ; increment the remainder
01 D1        add     ecx, edx    ; add this roll result to the accumulator
39 F2        cmp     edx, esi    ; see if this roll result should cause an explosion
74 F2        jz      Roll        ; if so, re-roll; otherwise, fall through
C3           ret                 ; return, with result in ECX register

有点作弊。所有标准的x86调用约定都会在EAX寄存器中返回函数的结果。但是,在真实的机器代码中,没有调用约定。您可以使用任何想要的输入/输出寄存器。使用ECX输出寄存器为我节省了1个字节。如果要使用EAX,请在XCHG eax, ecx指令前插入一个1字节的ret指令。这会交换EAXECX寄存器的值,从而有效地将结果从复制ECXEAX,并ECX用的旧值进行填充EAX

在线尝试!

这是在C中转录的等效函数,使用GCC,Clang和ICC支持__builtin_ia32_rdrand32_step内部函数生成RDRAND指令:

#include <immintrin.h>

unsigned int RollExplodingDie(unsigned int dieSize)
{
    unsigned int result = 0;
Roll:
    unsigned int roll;
    __builtin_ia32_rdrand32_step(&roll);
    roll    = ((roll % dieSize) + 1);
    result += roll;
    if (roll == dieSize)   goto Roll;
    return result;
}

有趣的是,带有-Os标志的GCC将其转换为几乎完全相同的机器代码。它接受输入EDI而不是ESI,这是完全任意的,并且对代码没有任何实质性影响。EAX正如我前面提到的,它必须以返回结果,并且它使用更有效(但更大)的MOV指令在之前执行此操作RET。否则,相同。当过程完全可逆时,总是很有趣:以汇编的形式编写代码,将其转录为C,通过C编译器运行它,然后使原始的汇编退出!


12

Python 2中66个 64 61字节

-3个字节感谢xnor

f=lambda n,c=0:c%n or c+f(n,randint(1,n))
from random import*

在线尝试!

上一卷存储在中c,这使我们可以多次访问它而不必将其存储到变量中,这在Python lambda中是无法做到的。每次递归时,我们检查是否滚动爆炸骰子。

c被初始化为零,所以c%n那里是假的。在接下来的迭代中,只有掷出了爆炸骰子,它才会是假的。

Python 2,55个字节

f=lambda n:randint(1,n)%n or n+f(n)
from random import*

在线尝试!

我的其他答案似乎有点过分设计,因为这似乎也行得通……我还是不管了。


2
中断条件基于随机性的递归函数将始终具有非零的堆栈溢出机会。从统计学上来说,机会微不足道,但仍然...
mypetlion

3
通常,根据我的经验,在代码打高尔夫球的挑战中,假定堆栈大小是无限的。随着堆栈大小增加到无穷大,堆栈溢出的可能性迅速收敛到零。
ArBo

在您输入评论之前,ArBo做@mypetlion,以便您可以ping用户
MilkyWay90

1
我认为c*(c<n)可以c%n
xnor

@xnor当然,我是个白痴…
ArBo

12

R,39个字节

n=scan();n*rgeom(1,1-1/n)+sample(n-1,1)

在线尝试!

说明:此解决方案通过直接计算将要发生的爆炸次数的分布来避免递归/ while循环。设n为裸片的边数。如果将成功表示为n滚动而将失败表示为其他任何滚动,则您的概率为1n成功的 n。爆炸总数是第一次失败之前的成功次数。这对应于Geometric(11n)分发(请参见Wikipedia页面,从相反的方向定义成功和失败)。每次爆炸都会使总数达到n。最终辊遵循Uniform(1,2,,n1)分配,我们添加到总。


非常好!一定喜欢内置的发行版来应对随机挑战!
朱塞佩

是否sample满足随机性的标准,因为它的偏见
西安

@西安可以肯定的:它是用于离散随机变量的内置随机生成器。
罗宾·赖德

我知道,我知道,但是请检查我所施加的链接:固有的离散sample导致缺乏一致性,使得最大概率与最小概率之比高达1.03 ...令人震惊,不是吗!
西安

是的,这令人震惊。但话又说回来,你经常使用sample?;-)m231
罗宾·赖德

9

Perl 6、26个字节

{sum {roll 1..$_:}...*-$_}

在线尝试!

说明

{                        } # Anonymous block
                  ...      # Sequence constructor
     {roll 1..$_:}         #   Next elem is random int between 1 and n
                           #   (Called as 0-ary function with the original
                           #   $_ for the 1st elem, then as 1-ary function
                           #   with $_ set to the previous elem which
                           #   equals n.)
                     *-$_  #   Until elem not equal to n (non-zero difference)
 sum                       # Sum all elements

2
好的,我自己的解决方法是{sum roll(*,1..$_)...$_>*}
Jo King

9

J16 11字节

(+$:)^:=1+?

在线尝试!

说明

TL; DR 1+?执行模头滚动,(+$:)^:=仅在等于输入时重复。


该函数是由四个动词组成的系列:

             ┌─ + 
         ┌───┴─ $:
  ┌─ ^: ─┴─ =     
  │               
──┤      ┌─ 1     
  └──────┼─ +     
         └─ ?     

火车是两个或多个动词串联而成的。在这里,答案的形式为f g h j

(+$:)^:=  1  +  ?
    f     g  h  j

所谓的“ 4-train”被解析为钩子和叉子:

f g h j   ⇔   f (g h j)

因此,答案等同于:

(+$:)^:= (1 + ?)

挂钩:(f g) xx (f g) y

给定参数为两个动词的单子(单参数)钩子x,以下等价成立:

(f g) x   ⇔   x f (g x)

例如,(* -) 5计算为5 * (- 5),计算为_25

这意味着我们的4列火车(f和的一个钩子(g h j))等效于:

(f (g h j)) x   ⇔   x f ((g h j) x)

但是f这里做什么呢?(+$:)^:=是使用Power合词的两个动词的合词^::另一个钩子((+$:))和一个动词(=)。请注意,这里f二进位的 -它有两个参数(x(g h j) x)。因此,我们必须查看^:行为方式。幂级联f^:o采用一个动词f和一个动词或一个名词o(名词只是一条数据)并应用f o时间。例如,以o = 3。以下等价成立:

(f^:3) x     ⇔   f (f (f x))
x (f^:3) y   ⇔   x f (x f (x f y))

如果o是动词,则幂连词将仅对o参数进行求值,并将名词结果用作重复计数。

对于我们的动词,o=等价动词。它0针对不同的论点和1相等的论点进行求值。(+$:)对于相同的参数,我们只对钩子重复一次,对于不同的参数,则不需重复。为了便于说明,让y ⇔ ((g h j) x)。请记住,我们的初始钩子与此等效:

x   (+$:)^:=   ((g h j) x)
x   (+$:)^:=   y

扩展连接,将变为:

x ((+$:)^:(x = y)) y

如果xy相同,则变为:

x (+$:)^:1 y   ⇔   x (+$:) y

否则,它将变为:

x (+$:)^:0 y   ⇔   y

现在,我们已经看到了单子叉子。在这里,我们有一个二叉叉:

x (f g) y   ⇔   x f (g y)

因此,当xy相同时,我们得到:

x (+$:) y   ⇔   x + ($: y)

什么$:啊 它指的是整个动词本身,并允许递归。这意味着,何时x和y are the same, we apply the verb toy and addx`。

货叉: (g h j) x

现在,内叉有什么作用?这是y我们的最后一个例子。对于三个动词的单子叉,给定参数x,以下等价成立:

(g h j) x   ⇔   (g x) h (j x)

在下一个示例中,假设我们有名为,和的动词SUM,它们会做您认为可能的事。如果将三个连接成一个叉,我们将得到:DIVIDELENGTH

(SUM DIVIDE LENGTH) x   ⇔   (SUM x) DIVIDE (LENGTH x)

此分叉求和的平均值x(假设x是数字列表)。在J中,我们实际上将其写为+/ % #

关于叉子的最后一件事。当最左边的“ tine”(在上面的符号情况下为g)是名词时,它将被视为返回该值的常数函数。

完成所有这些操作后,我们现在可以了解上述分支:

(1 + ?) x   ⇔   (1 x) + (? x)
            ⇔   1 + (? x)

?[0,x)[1,x]

放在一起

考虑到所有这些事情,我们的动词等同于:

((+$:)^:=1+?) x   ⇔   ((+$:)^:= 1 + ?) x
                  ⇔   ((+$:)^:= (1 + ?)) x
                  ⇔   x ((+$:)^:=) (1 + ?) x
                  ⇔   x ((+$:)^:=) (1 + (? x))
                  ⇔   x (+$:)^:(x = (1 + (? x))
(let y = 1 + (? x))
if x = y          ⇒   x + $: y
otherwise         ⇒   y

这表示所需的功能。


1
(+$:)^:=1+?­­
ngn

@ngn谢谢!成立。
科纳·奥布赖恩

7

果冻,7个字节

X+ß}¥=¡

在线尝试!

使用递归。再次运行程序(ß),+如果(¡)随机数(X)等于(=),则将()添加到程序输入中。}品牌ß作用于程序输入和¥联合+ß}成一个单一的链路为¡消耗。

在这里,我使用程序收集了n = 6的1000个输出的分布。用python / matplotlib绘制。 直方图

这是在半对数图上从n = 3的5000个数据点,该图显示了(大约?)指数分布。 在此处输入图片说明


好地块!您得到的分布是几何分布(请参见我的R答案),它指数分布密切相关
罗宾·赖德

6

Pyth- 12 11字节

在功能中使用。我觉得应该有一个更聪明的答案来模拟分布。

-.W!%HQ+hOQ

-         (Q)         Subtract Q. This is because we start Z at Q to save a char
 .W                   While, functionally
  !                   Logical not. In this case, it checks for 0
   %HQ                Current val mod input
  +     (Z)           Add to current val
   h                  Plus 1
    OQ                Random val in [0, input)

在线尝试


4

Python 3,80个字节

import random as r,math
lambda n:int(-math.log(r.random(),n))*n+r.randint(1,n-1)

在线尝试!


1
如果r.random()碰巧返回0 1-r.random(),则有少许失败的机会。
nwellnhof

尽管从技术上讲,机会是0
Quintec

1
import ... as _最短的罕见情况!
xnor

确实是@xnor!唯一的其他时间我记得在我的答案胜出就是在这里
林恩

4

05AB1E,10 个字节

[ILΩDIÊ#}O

在线尝试验证列表

10字节替代:

[LΩDˆÊ#}¯O

在线尝试验证列表

尽管我更喜欢排名靠前的一词,因为它包含了“单词” DIÊ,这很适合挑战。

说明:

[         # Start an infinite loop:
 IL       #  Create a list in the range [1, input]
   Ω      #  Pop and push a random value from this list
    D     #  Duplicate it
     IÊ   #  If it's NOT equal to the input:
       #  #   Stop the infinite loop
}O        # After the loop: sum all values on the stack
          # (which is output implicitly as result)

[         # Start an infinite loop
 L        #  Create a list in the range [1, (implicit) input]
  Ω       #  Pop and push a random value from this list
   Dˆ     #  Add a copy to the global_array
     Ê    #  If it's NOT equal to the (implicit) input:
      #   #   Stop the infinite loop
        # After the loop: push the global_array
  O       # Pop and push its sum
          # (which is output implicitly as result)  

正在尝试思考一种使用方式或其他方式。
魔术章鱼缸


3

R47 42字节

function(n){while(!F%%n)F=F+sample(n,1)
F}

在线尝试!

感谢ARBO的做法

比罗宾·赖德(Robin Ryder)还要长一个字节,请投票支持他!


有趣的是,我将其改写if为46字节的递归,但最终在一次滚动中得到52,这在n = 4时是不可能的,所以我不知道是否发生了奇怪的低递归限制事情,但是我认为这可能是越野车。在线尝试!
刑事

我尝试了递归并得到了54字节的解决方案。然后尝试与您类似的东西进行44次在线尝试!
亚伦·海曼



3

Haskell77 76字节

import System.Random
f x=randomRIO(1,x)>>=(x!)
x!y|y<x=pure y|0<1=(y+)<$>f x

在线尝试!

感谢killmous的一个字节。

如果<|>在序幕中,我们可以做得更好MonadComprehensions

Haskell,非竞争性,66个字节

import System.Random
f x=do y<-randomRIO(1,x);[y|y<x]<|>(y+)<$>f x

在线尝试!


1
如果将g定义为中缀函数,则可以保存一个字节。
杀死

1
@killmous,谢谢。乍一看,我认为这是相同的或更糟的,但是更好。
dfeuer

3

Python 2,53字节

f=lambda n:random()*n//1or n+f(n)
from random import*

在线尝试!

使用ArBo的答案中or的短路思想。表达方式random()*n//1生成一个从0到的数字n-1,并0代替一卷n。在or采取这一数字,但如果是零(Falsey)继续上n+f(n)


当我在较短的那个中进行编辑时,您的答案似乎已经结束了……我没有看到此信息,但是如果您要我删除它,因为它与我将非常相似。
ArBo

@ArBo完全没有,独立重复的答案也可以
xnor

3

杰普特,13个字节

ö)g@¶°X?X+ß:X

试试吧

的港口 阿诺尔德的答案。弄清楚如何进行递归调用;)

转译的JS:

// U: Implicit input
// ö: generate a random number [0,U)
(U.ö())
  // g: run the result through a function
  .g(function(X, Y, Z) {
    // increment the result and compare to input
    return U === (++X)
      // if they are the same, roll again and add to current roll
      ? (X + rp())
      // if they are different, use current roll
      : X
   })

1
非常好用的N.g(f):)
Shaggy

我自己动了一下,最后得到了12个字节,但我不想发布它,因为我太喜欢您的解决方案了!
毛茸茸的

将其发布为其他答案:)
dana

这可能是短,但它的很多丑陋的比你的地狱:petershaggynoble.github.io/Japt-Interpreter/...
长毛

我知道-是的,我正试图提出一种不污染的方法U。跳过一条线似乎也可以。那是个好主意:)
dana

3

杰普特,12个字节

它可能比dana的解决方案要短,但这是一个丑陋的地狱。我只发布它是因为自从我们有了以空行开头的Japt解决方案以来,它似乎永远存在。


ö
>°V©VªV+ß

试试吧


2

PowerShell,49字节

for($a=$l="$args";$a-eq$l){$o+=$l=1..$a|Random}$o

在线尝试!

迭代方法。将输入设置$args$a$last滚动(完成后,我们至少进入循环一次)。然后,只要最后一个滚动-eq与输入一致,我们就继续滚动。在循环中,我们累积到$o最后一卷,通过创建从1到输入的范围$a并选择其中的一个Random元素来进行更新。(老实说,我对此$o+=$l=工作感到有些惊讶。)一旦我们退出循环,我们就离开$o了管道,并且输出是隐式的。


2

第四(gforth),72字节

include random.fs
: f >r 0 begin i random 1+ >r i + r> i < until rdrop ;

在线尝试!

代码说明

include random.fs      \ include library file for random
: f                    \ start a new word definition
  >r                   \ stick the input on the return stack (for easy access)
  0                    \ add a counter to hold the sum
  begin                \ start an indefinite loop
    i random 1+        \ generate a random number from 1 to n
    >r i + r>          \ add the result to the counter, use the return stack to save a few bytes
    i <                \ check if result was less than n
  until                \ end the loop if it was, otherwise go back to begin
  rdrop                \ remove n from the return stack
;                      \ end the word definition

2

批处理,70字节

@set t=0
:g
@set/at+=d=%random%%%%1+1
@if %d%==%1 goto g
@echo %t%

将输入n作为命令行参数%1d是当前滚动,t累计总数。一直滚动直到d不等于n


2

果冻,9字节

x⁹X€Ä%ƇµḢ

在线尝试!

以n为参数并返回由爆炸的n边模具生成的数字的单子链接。这将生成256个从1到n的数字,并返回不是n的倍数的第一个累积和。从理论上讲,这可能返回256n,但是即使对于2面芯片,这种情况也只会每发生一次2256次。

没有此限制的替代方法是:

果冻,10字节

X³X¤+¥³ḍ¥¿

在线尝试!

请注意,两个TIO链接都生成400个数字以显示分布。


2

Python 3中81 72个字节

from random import*
def f(x,a=0):
 while a%x<1:a+=randint(1,x)
 return a

在线尝试!

-9字节归功于ArBo

说明

import random             #load the random module              
def explodeDice(num):     #main function
    ans = 0                     #set answer to 0
    while a % num != 0:         #while a isn't a multiple of the input
        ans += random.randint(1, num) #add the next dice roll to answer
    return ans                  #return the answer

您可以from random import*改用节省1个字节。
Orthoplex

1
您可以使用递归解决方案将其减少到74个字节
恢复莫妮卡

1
@squid您可以像保存1个字节这样
Orthoplex

1
@orthoplex,然后您可以缩短if / else并将其变成单线。然后开始看起来像我的解决方案;)
ArBo

1
@ArBo是的,这就是为什么我没有更改为递归的原因,不想只复制您。
Artemis

2

TI-BASIC,28 23字节

-5个字节,感谢 meta post!

Ans→N:0:Repeat fPart(Ans/N:Ans+randInt(1,N:End:Ans

输入为Ans
输出在其中Ans并隐式打印。

例子:

4
              4
prgmCDGF11
              5
6
              6
prgmCDGF11
              3

说明:

Ans→N:0:Repeat fPart(Ans/N:Ans+randInt(1,N:End:Ans   ;full logic

Ans→N                                                ;store the input in "N"
      0                                              ;leave 0 in "Ans"
        Repeat fPart(Ans/N                 End       ;loop until the sum
                                                     ; is not a multiple of
                                                     ; the input
                               randInt(1,N           ;generate a random
                                                     ; integer in [1,N]
                           Ans+                      ;then add it to "Ans"
                                               Ans   ;leave the sum in "Ans"
                                                     ;implicitly print "Ans"

笔记:

  • TI-BASIC是一种标记化语言。字符数不等于字节数。

由于startTmr不再需要此提交,因此现在该提交将可用于TI-BASIC早于TI-84 +的版本
Tau

2

SmileBASIC 3,49个字节

函数D N OUT R递归实现爆炸骰子滚动。

DEF D N OUT R
R=RND(N)+1IF R==N THEN R=R+D(N)
END

不打高尔夫球

DEF D N OUT R  'N is sides and R is output param (shorter than using RETURN in this case)
 R=RND(N)+1  'random number in [1, N]
 IF R==N THEN R=R+D(N)  'if roll is same as N then roll again and add
END

请注意,在SmileBASIC中,函数可以具有多个返回值。如果一个函数有一个返回值,那么fun in OUT varvar = fun(in)是完全一样的,这就是为什么我们可以定义函数OUT形式,并把它在函数体本身的表达。如果我将函数定义为DEF D(N)必须RETURN R在函数主体中明确声明;混合使用两种语法可以节省字节数。




2

SmileBASIC,41个字节

INPUT N@L
S=S+RND(N)+1ON S MOD N GOTO@L?S

看完之后:

请注意,永远不要输出n的任何倍数,因为它们总是会爆炸。

我意识到,与其检查骰子是否掷骰子,不如检查骰子n的总和是的倍数就可以重复n


2

任何骰子,36个字节

该语言几乎是内置的:

function:f I:n{result: [explode dI]}

为此,我必须滥用无限递归深度假设。AnyDice使用全局属性最大函数深度限制递归深度。内置的爆炸使用了它自己的;爆炸深度-默认为2。

set "explode depth" to 99

将再增加25个字节;并不能真正满足要求,因为理论上骰子爆炸可能超过99次。

该函数的输出是一个模具,即。AnyDice内置类型,它是结果和结果概率的配对。


1
我认为我认为它不会爆炸太多,我认为36字节就可以了。我没有说没有内建函数,我可以在这里安装它们,因为您的1字节或0字节的答案并不成功。但欢迎来到该网站!
Rɪᴋᴇʀ

2

CJam,19个字节

qi{__mr)_T+:T;=}g;T

说明:

T is pre-set to 0

qi{__mr)_T+:T;=}g;T - whole code
qi                  - read input as integer (n) | Stack: n
  {            }    - block
   __               - Duplicate twice | Stack: n n n
     mr)            - Choose a random number from 1 to n (r). Since 'mr' picks a
                      number from 0 to n-1, the number has to be incremented with ')' 
                      Stack: n n r
        _           - Duplicate |  Stack: n n r r
         T          - push T | Stack: n n r r T
          +         - add the random number to T (t) | Stack: n n r t
           :T;      - pop the value and store in T | Stack: n n r
              =     - are the top two stack values the same (c) | Stack: n c
               }
                g   - do while loop that pops the condition from the stack after each
                      iteration | Stack: n
                 ;  - pop the top stack element and discard | Stack: T
                  T - push T | Stack: T
                    - implicit output

或使用伪代码:

input n
var sum = 0
do {
    var random_number = pick random number from 1 to n
    sum = sum + random_number
} while (random_number == n)
output n

作为流程图:

代码流程图

在线尝试!


2

Excel VBA,46个字节

感谢@TaylorScott

Do:v=-Int(-[A1]*Rnd):t=t+v:Loop While[A1]=v:?t

在命令窗口中执行。

作为用户定义的功能。

Excel VBA,108 67字节

Function z(i)
Do
v=Int((i*Rnd)+1)
z=z+v
Loop While v=i
End Function

您可以通过使用do.. loop while循环并将该函数转换为即时窗口函数来解决这一问题。- Do:v=-Int(-[A1]*Rnd):t=t+v:Loop While[A1]=v:?t- 46字节
泰勒斯科特

1
@TaylorScott谢谢,我忘记了在Excel VBA中存在x x y。
威廉·波特
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.