建立一个沙堆


59

一个阿贝尔沙堆,对于我们的目的,是整数坐标无限电网,初始为空沙。每秒之后,将一粒沙粒放在(0,0)。只要网格单元有4个或更多的沙粒,它就会同时向其四个邻居中的每一个洒一个沙粒。(x,y)的邻居是(x-1,y),(x + 1,y),(x,y-1)和(x,y + 1)。

当一个细胞溢出时,可能导致其邻居溢出。一些事实:

  • 此级联最终将停止。
  • 细胞溢出的顺序无关紧要。结果将是相同的。

3秒后,网格看起来像

.....
.....
..3..
.....
.....

4秒后:

.....
..1..
.1.1.
..1..
.....

15秒后:

.....
..3..
.333.
..3..
.....

在16秒后:

..1..
.212.
11.11
.212.
..1..

挑战

用尽可能少的字节编写一个函数,该函数采用单个正整数t并在t秒后输出沙堆的图片。

输入项

您选择的任何格式的单个正整数t

输出量

t秒钟后使用字符显示沙堆的图片

 . 1 2 3

编辑:使用任何您喜欢的四个不同字符,或绘制图片。如果您未使用“ .123”或“ 0123”,请在答案中指定字符的含义。

与示例不同,您的输出应包含显示沙堆的非零部分所需的最少行和列数。

也就是说,对于输入3,输出应为

 3

对于4,输出应为

 .1.
 1.1
 .1.

计分

适用标准高尔夫计分。

规则

不允许使用已经知道什么是沙堆的语言功能或库。

编辑:输出部分已被编辑,字符集限制已完全解除。使用任意四个不同的字符或颜色。


2
输入t可以0吗?那么输出是多少?
路易斯·门多

1
在给定的时间步中是否可以连续进行多个级联是否正确?那么在那个时间步长中,级联不断发生,直到每个单元格都等于或小于3为止?
瑕疵的2016年

2
@flawr:是的,这是正确的。看一下t = 15和t = 16之间的差异。
El'endia Starman

@LuisMendo输入被指定为正t,所以零不是有效输入。
埃里克·特雷斯勒

1
.空单元真的有必要吗?我们可以有0一个有效的空单元格吗?
安德烈KOSTYRKA

Answers:


56

R,378个 343 297 291字节

通常,用户通过scan()(我已经使用过变量t,所以让我们z取而代之)提供他/她的输入,因此第二行应分别启动,然后其余部分启动:

e=numeric
a=1%*%scan()
x=1
o=a>3
n=1
while(any(o)){
v=which(o,T)
if(any(v==1)){a=rbind(e(n+2),cbind(e(n),a,e(n)),e(n+2));x=x+1;n=n+2;v=which(a>3,T)}
q=nrow(v)
u=cbind(e(q),1)
l=v-u[,1:2];r=v+u[,1:2];t=v-u[,2:1];b=v+u[,2:1]
a[l]=a[l]+1;a[r]=a[r]+1;a[t]=a[t]+1;a[b]=a[b]+1
a[v]=a[v]-4
o=a>3}
a

输出包含的值的数组at次生成(0,1,2或3)。

测试用例:

z=3
     [,1]
[1,]    3
z=4
     [,1] [,2] [,3]
[1,]    0    1    0
[2,]    1    0    1
[3,]    0    1    0
z=16
     [,1] [,2] [,3] [,4] [,5]
[1,]    0    0    1    0    0
[2,]    0    2    1    2    0
[3,]    1    1    0    1    1
[4,]    0    2    1    2    0
[5,]    0    0    1    0    0

这有助于我们使该对象在垂直和水平方向上对称,这意味着最左边的点的高度为4,这意味着最上面,最右边和最下面的点也为4。

哦,我是说您可以做出漂亮的视觉效果吗?

1000滴后:

1000步后的Abelian沙堆

50000滴后(≈4秒):

50000步骤后的Abelian沙堆

在333333次掉落(≈15分钟)之后:

100000步骤后的Abelian沙堆

您也可以绘制它!

image(1:n,1:n,a,col=colorRampPalette(c("#FFFFFF","#000000"))(4), axes=F, xlab="", ylab="")

这件事花了4秒钟进行10000次迭代,但是对于较大的阵列大小(例如,几分钟进行100000次迭代),速度大大降低。这就是为什么它变得如此缓慢的原因(我估算了增长率增长率,得到τ(i)≈689·i ^ 1.08,因此,每增加一个谷物,直到整个沙堆在i步骤之后沉降的平均时间略大于一个)。 ,并且作为晶粒数量的函数的总时间比平方增长(T(i)≈0.028* i ^ 1.74)慢一点:

平均迭代,直到桩沉降

大概的计算时间

现在有一个完整的解释:

e=numeric # Convenient abbreviation for further repeated use
a=1%*%scan() # Creates a 1×1 array with a user-supplied number
x=1 # The coordinate of the centre
o=a>3 # Remember which cells were overflown
n=1 # Array height that is going to change over time
while(any(o)){ # If there is still any overflow
  v=which(o,T) # Get overflown cells' indices
  if(any(v==1)){ # If overflow occurred at the border, grow the array
    a=rbind(e(n+2),cbind(e(n),a,e(n)),e(n+2)) # Growing
    x=x+1 # Move the centre
    n=n+2 # Change the height
    v=which(a>3,T) # Re-index the overflowed cells
    }
  q=nrow(v) # See how many indices are overflown
  u=cbind(e(q),1) # Building block for neighbours' indices
  l=v-u[,1:2];r=v+u[,1:2];t=v-u[,2:1];b=v+u[,2:1] # L, R, T, B neighbours
  a[l]=a[l]+1;a[r]=a[r]+1;a[t]=a[t]+1;a[b]=a[b]+1 # Increment neighbours
  a[v]=a[v]-4 # Remove 4 grains from the overflown indices
  o=a>3} # See if still overflown indices remain
a # Output the matrix

这是我生命中的第一次,成长对象(如a <- c(a, 1))的工作速度比为值预先分配一个大的空矩阵并用大量未使用的零逐渐填充它要快得多。

更新。通过去除Golfed 18个字节arr.indwhich由于Billywob和更换rep(0,n)e=numeric;e(n)在由于5个实例JDL,和17个由于字节JDL

更新2.由于沙堆是Abelian,它可能从所需高度的堆栈开始,因此我删除了多余的循环,从而极大地提高了生产率!


1
我对您要输出的额外列,行索引的观点是正确的,但是我想将输出限制为“答案”,仅此而已。不过,很高兴您附上图片。
埃里克·特雷斯勒

1
很好的答案安德烈!不过,您肯定可以打几个字节,例如预定义rep(),因为您要使用6次。其次,我认为您不需要写出arr.ind=Twhich()功能的选项。只需使用which(...,T)
Billywob

1
由于定义的字符数少于n=numeric,因此定义和使用它可能更容易。我喜欢图片。n(k)r(0,k)
JDL

1
另一个建议:1%*%0字符数少于array(0,c(1,1))。另外,to的第二个参数u <- cbind只能为1,cbind默认情况下会将其扩展为第一个参数的长度。
JDL

1
@GregMartin修复了此问题。抱歉 在我的第一种语言中,我们使用“自我的”一词,而从不理会所讨论的人的性别(例如“一个人的一小步”);仍然,有时,在极少数情况下,我称狗为“她”或“他”,而应为“它”,除非您是主人,并且您确实想强调肛门的性别(尽管告诉男性和女性并不难)。
安德烈KOSTYRKA

13

MATL55 53 48 43 42字节

受到@flawr的回答的启发。

图形输出

0i:"Gto~+XytP*+t"t4=t1Y6Z+b+w~*]]tat3$)1YG

在MATL在线上尝试一下!。输入大约需要10秒钟30。您可能需要刷新页面,如果不起作用,请再次按“运行”。

这是输入的示例结果100

在此处输入图片说明

ASCII输出(43个字节)

0i:"Gto~+XytP*+t"t4=t1Y6Z+b+w~*]]tat3$)48+c

在线尝试!

说明

0          % Push a 0. This is the initial array. Will be resized in first iteration
i:         % Take input n. Generate range [1 2 ... n]
"          % For each, i.e. repeat n times
  Gto~+    %   Push input and add negate parity. This "rounds up" n to odd number
           %   m = n or n+1
  Xy       %   Identity matrix with that size
  tP*      %   Multiply element-wise by vertically flipped copy. This produces a
           %   matrix with a 1 in the center and the rest entries equal to 0
  +        %   Add to previous array. This updates the sandpile array
  t        %   Duplicate
  "        %   For each column (i.e. repeat m times)
    t4=    %     Duplicate. Compare with 4 element-wise. This gives a 2D mask that
           %     contains 1 for entries of the sandpile array that equal 4, and 0
           %     for the rest
    t      %     Duplicate
    1Y6    %     Predefined literal: [0 1 0; 1 0 1; 0 1 0]
    Z+     %     2D convolution, maintaining size
    b      %     Bubble up to bring sandpile array to top
    +      %     Element-wise addition. This adds 1 to the neighbours of a 4
    w      %     Swap to bring copy of mask to top
    ~*     %     Multiply bu negated mask. This removes all previous 4
  ]        %  End
]          % End
t          % Duplicate the updated sandpile array
a          % 1D mask that contains 1 for columns that contain a 1. This will be
           % used as a logical index to select columns
t          % Duplicate. This will be used as logical index to select rows (this
           % can be done because of symmetry)
3$)        % Keep only those rows and columns. This trims the outer zeros in the
           % sandpile array
1YG        % Display as scaled image

3
我嫉妒1Y6
瑕疵的

1
@flawr但您~mod(spiral(3),2)却更聪明了:-)
Luis

11

MATLAB,160个156 148字节的

n=input('');z=zeros(3*n);z(n+1,n+1)=n;for k=1:n;x=z>3;z=z+conv2(+x,1-mod(spiral(3),2),'s');z(x)=z(x)-4;end;v=find(sum(z));z=z(v,v);[z+48-(z<1)*2,'']

首先,创建一个太大的矩阵,n中间的某个地方。然后,通过非常方便的2d卷积计算级联。最后,多余的部分会被修剪掉,整个东西会转换为字符串。

输出示例 t=100

...121...
..32.23..
.3.323.3.
123.3.321
2.23.32.2
123.3.321
.3.323.3.
..32.23..
...121...

一如既往:

卷积是成功的关键。


v=any(z)而不是v=find(sum(z))(我在答案中使用的是)。同样,2*~z代替(z<1)*2
Luis Mendo

我的计算机在输入时冻结了n=500...它已经处理n=400了几秒钟。难道我做错了什么?
安德烈KOSTYRKA

@AndreïKostyrka它为我工作(Matlab R2015b)
Luis

1
@AndreïKostyrka n该程序的输入生成一个3*n x 3*n矩阵,因此它需要存储大约9*n^2数字。这也是完全无效的,因为我们也有从1到n的完全不必要的长迭代。但是再说一次,这就是代码高尔夫,使程序高效运行是另一回事。
瑕疵的

@AndreïKostyrka您可以使用稀疏矩阵(第二行:)z=sparse(zeros(2*n+1))并将for循环更改为,以提高内存效率while any(z(:)>3)。然后,您也可以只计算一次卷积内核:kern = 1-mod(spiral(3),2)
瑕疵的

9

蟒蛇2,195 +1 +24 = 220217

from pylab import*
ifrom scipy.signal import convolve2d as c
k=(arange(9)%2).reshape(3,3)
def f(n):g=zeros((n,n),int);g[n/2,n/2]=n;exec"g=c(g/4,k,'same')+g%4;"*n;return g[any(g,0)].T[any(g,0)]

n = 16的输出

array([[0, 0, 1, 0, 0],
       [0, 2, 1, 2, 0],
       [1, 1, 0, 1, 1],
       [0, 2, 1, 2, 0],
       [0, 0, 1, 0, 0]])

通过将其n用作“足够好”的上限,存在大量不必要的填充和迭代,但是n = 200仍在一秒钟内完成,n = 500约在12秒内完成

不打高尔夫球

from pylab import*
from scipy.signal import convolve2d as c
k=array([0,1,0],
        [1,0,1],
        [0,1,0])
def f(n):
  g=zeros((n,n))                 # big grid of zeros, way bigger than necessary
  g[n/2,n/2]=n                   # put n grains in the middle
  exec"g=c(g/4,k,'same')+g%4;"*n # leave places with <4 grains as is, convolve the rest with the kernel k, repeat until convergence (and then some more)
  return g[any(g,0)].T[any(g,0)] # removes surrounding 0-rows and columns

替换return ximshow(x)添加一个字符并输出难​​看的插值图像,添加imshow(x,'gray',None,1,'nearest')删除模糊的插值,使输出达到规格

n = 100


运行您的代码时出现以下错误:ImportError: No module named convolve2d。更改import scipy.signal.convolve2d as cfrom scipy.signal import convolve2d as c可解决问题。我正在使用scipy版本0.16.1,我需要旧版本还是新版本?还是其他问题?
Andrew Epstein 2016年

很奇怪,现在您提到它对我也不起作用了。我可能第一次在交互模式下做对了,然后将其缩短并忽略了错误,但该功能仍保留在内存中
DenDenDo

6

Perl,157147字节

包括+1的 -p

在STDIN上运行计数,使用0123到STDOUT 打印地图:

sandpile.pl <<< 16

sandpile.pl

#!/usr/bin/perl -p
map{++substr$_,y///c/2-1,1;/4
/?$.+=s%^|\z%0 x$..$/%eg+!s/\b/0/g:s^.^$&%4+grep{3<substr$\,0|$_+"@+",1}-$.-2,-2,0,$.^eg while/[4-7]/}($\="0
")x$_}{

5

3 2,418个 385 362 342 330字节

w='[(i,j)for i in r(n)for j in r(n)if a[i][j]>3]'
def f(z):
 a,x,r=[[z]],0,range
 for _ in[0]*z:
  n=len(a);v=eval(w)
  if[1for b,c in v if(b==0)+(c==0)]:n+=2;a=[[0]*n]+[[0]+a[i]+[0]for i in r(n-2)]+[[0]*n];x+=1;v=eval(w)
  for c,d in v:exec'a[c+%s][d+%s]+=1;'*4%(-1,0,1,0,0,-1,0,1);a[c][d]-=4
 for i in a:print''.join(map(str,i))

编辑:由于@ Qwerp-Derp,节省了6个字节

全部归功于@AndreïKostyrka,因为这是他的R代码直接翻译成Python的结果。


我认为您可以将的分配a,x,r移入函数参数中。
Loovjo '16

1
我已经将您的代码压缩了几个字节……虽然不多,但是必须这样做。您是否介意我对答案进行编辑以及是否将Python版本更改为Python 2?
Qwerp-Derp

@ Qwerp-Derp:放心!我很想看看你做了什么。
安德鲁·爱泼斯坦

3

JavaScript中,418个 416 406 400 393字节

创建一个匿名函数,在控制台上显示输出。

var f =
    t=>{a=(q,w)=>Math.max(q,w);c=_=>{x=a(p[0],x);y=a(p[1],y);m[p]=(g(p)+1)%4;if(!m[p]){s.push([p[0],p[1]]);}};x=y=0,m={};g=k=>{v=m[k];return!v?0:v;};m[o=[0,0]]=1;s=[];while(--t){m[o]=(m[o]+1)%4;if(!m[o]){s.push(o);}while(s.length){p=s.pop();p[0]++;c();p[0]-=2;c();p[0]++;p[1]++;c();p[1]-=2;c();p[1]++;}}s='';for(i=-x;i<=x;i++){for(j=-y;j<=y;j++){v=g([i,j]);s+=v==0?'.':v;}s+='\n';}console.log(s);}
<input id="i" type="number"><input type="button" value="Run" onclick="var v = +document.getElementById('i').value; if (v>0) f(v)">


1
警告:我没有输入就按了“运行”,屏幕崩溃了(无限循环)。不要像我以前那样傻。
roberrrt-s

1
@Roberrrt我更新了答案以防止这种情况。
hetzi

3

Nim,294个字符

import os,math,sequtils,strutils
var
 z=parseFloat paramStr 1
 y=z.sqrt.toInt+1
 w=y/%2
 b=y.newSeqWith newSeq[int] y
 x=0
proc d(r,c:int)=
 b[r][c]+=1;if b[r][c]>3:b[r][c]=0;d r-1,c;d r,c+1;d r+1,c;d r,c-1
for i in 1..z.toInt:d w,w
while b[w][x]<1:x+=1
for r in b[x..< ^x]:echo join r[x..< ^x]

编译并运行:

nim c -r sandbox.nim 1000

笔记:

  1. 我能够提出一个较短的版本,该版本使用固定的表大小,但是我将其编辑为动态表。
  2. 计算完沙箱后,x将其计算为中间行开始处的零列数。
  3. 为了显示,通过x从每一端排除行和列来对表进行切片。

性能

nim c --stackTrace:off --lineTrace:off --threads:off \ 
      --checks:off --opt:speed sandbox.nim

time ./sandbox   10000       0.053s
time ./sandbox   20000       0.172s
time ./sandbox   30000       0.392s
time ./sandbox   40000       0.670s
time ./sandbox  100000       4.421s
time ./sandbox 1000000    6m59.047s

3

Scala,274个字节

val t=args(0).toInt
val s=(Math.sqrt(t)+1).toInt
val (a,c)=(Array.ofDim[Int](s,s),s/2)
(1 to t).map{_=> ?(c,c)}
println(a.map{_.mkString}.mkString("\n"))
def?(b:Int,c:Int):Unit={
a(b)(c)+=1
if(a(b)(c)<4)return
a(b)(c)=0
?(b+1,c)
?(b-1,c)
?(b,c+1)
?(b,c-1)
}

用法:

scala sandpile.scala <iterations>

我认为对此没有太多解释。基本上,它只是向中心添加一粒沙。然后检查它是否大于4,如果是,它将溢出并检查所有大于4的邻居,等等。这非常快。

性能:

  • t = 10000 72毫秒
  • t = 20000 167毫秒
  • t = 30000 419ms
  • t = 40000 659ms
  • t = 100000 3413ms
  • t = 1000000大约6分钟

我的程序建议,以(0,0)为中心,沙堆首先在t = 1552处击中半径15。这将需要一个31x31的数组来存储(坐标-15至15)。您确定在t = 5000内这是正确的吗?
埃里克·特雷斯勒

我不确定这是正确的,尽管我认为我的逻辑正确吗?我在t> 5593上获得了超出范围异常的数组索引
AmazingDreams

当我增加然后立即检查是否溢出时,它确实在t = 1552处超出范围。我会说这是正确的实现。我更新了代码。
AmazingDreams

只有在C或Fortran中使用优化编译器的直接数组操作才能击败您的性能。我羡慕你。
安德烈KOSTYRKA

@AndreïKostyrka,是的,这是scala发光的地方!尽管我的输出不符合规范,所以我必须进行处理
AmazingDreams

2

J,76个字节

p=:0,.~0,.0,~0,]
p`(4&|+3 3([:+/@,*&(_3]\2|i.9));._3[:<.4%~p)@.([:*/4>{.)^:_

我定义了一个动词p,它在输入周围填充零的边界。主动词采用数组作为输入。然后,它检查第一行是否包含4个或更多颗粒的任何沙堆。如果使用,则输出使用填充的相同数组p,否则使用2d卷积以模拟掉落的晶粒。重复主动词,直到使用幂运算符收敛为止^:_

用法

   p =: 0,.~0,.0,~0,]
   f =: p`(4&|+3 3([:+/@,*&(_3]\2|i.9));._3[:<.4%~p)@.([:*/4>{.)^:_
   f 15
0 3 0
3 3 3
0 3 0
   f 50
0 0 0 1 0 0 0
0 0 3 1 3 0 0
0 3 2 2 2 3 0
1 1 2 2 2 1 1
0 3 2 2 2 3 0
0 0 3 1 3 0 0
0 0 0 1 0 0 0
   timex 'r =: f 50000'
46.3472
   load 'viewmat'
   ((256#.3&#)"0<.255*4%~i._4) viewmat r

计算n = 50000 的结果大约需要46秒,并且可以使用viewmat具有单色配色方案的插件显示结果。

数字


2

C 229(警告很多)

G[99][99],x,y,a=99,b=99,c,d;S(x,y){if(++G[y][x]>3)G[y][x]=0,S(x+1,y),S(x-1,y),S(x,y+1),S(x,y-1);a=x<a?x:a;b=y<b?y:b;c=x>c?x:c;d=y>d?y:d;}F(t){for(;t--;)S(49,49);for(y=b;y<=d;y++){for(x=a;x<=c;x++)printf("%d ",G[y][x]);puts("");}}

/* call it like this */
main(_,v)char**v;{F(atoi(v[1]));}

好的,我放弃:为什么您的数组是99 x 98?
埃里克·特雷斯勒

@EricTressler我怎么在测试中找不到呢?
杰里·耶利米


1

PHP,213字节

function d($x,$y){global$p,$m;$m=max($m,$x);$q=&$p[$y][$x];if(++$q>3){$q=0;d($x+1,$y);d($x-1,$y);d($x,$y+1);d($x,$y-1);}}while($argv[1]--)d(0,0);for($y=-$m-1;$y++<$m;print"\n")for($x=-$m;$x<=$m;)echo+$p[$y][$x++];

递归地产生堆$p,记住堆的大小$m; 然后使用嵌套循环进行打印。
用运行-r

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.