Construa uma pilha de areia

59

Uma pilha de areia abeliana , para nossos propósitos, é uma grade infinita com coordenadas inteiras, inicialmente vazias de areia. Após cada segundo, um grão de areia é colocado em (0,0). Sempre que uma célula da grade possui 4 ou mais grãos de areia, ela derrama um grão de areia para cada um de seus quatro vizinhos simultaneamente. Os vizinhos de (x, y) são (x-1, y), (x + 1, y), (x, y-1) e (x, y + 1).

Quando uma célula é derramada, pode causar derramamento de seus vizinhos. Alguns fatos:

  • Essa cascata acabará eventualmente.
  • A ordem na qual as células se derramam é irrelevante; O resultado será o mesmo.

Exemplo

Após 3 segundos, a grade parece

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

Após 4 segundos:

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

Após 15 segundos:

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

E depois de 16 segundos:

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

O desafio

No menor número de bytes possível, escreva uma função que use um único número inteiro positivo t e mostre uma imagem do monte de areia após t segundos.

Entrada

Um único número inteiro positivo t , em qualquer formato que você escolher.

Resultado

Uma imagem do monte de areia após t segundos, usando os caracteres

 . 1 2 3

Editar: use quaisquer quatro caracteres distintos que desejar ou faça um desenho. Se você não estiver usando ".123" ou "0123", especifique na sua resposta o que os caracteres significam.

Diferentemente dos exemplos, sua saída deve conter o número mínimo de linhas e colunas necessárias para mostrar a parte diferente de zero do sandpile.

Ou seja, para a entrada 3, a saída deve ser

 3

Para 4, a saída deve ser

 .1.
 1.1
 .1.

Pontuação

A pontuação de golfe padrão se aplica.

Regras

Não são permitidas funções de linguagem ou bibliotecas que já sabem o que é um monte de areia.

Editar: a seção de saída foi editada, a restrição do conjunto de caracteres foi completamente removida. Use quatro caracteres ou cores distintas que desejar.

Eric Tressler
fonte
2
Pode a entrada t ser 0? Qual é o resultado, então?
Luis Mendo
11
É correto que, em um determinado intervalo de tempo, várias cascatas possam ocorrer em uma linha? Então, nesse intervalo de tempo, as cascatas continuam acontecendo até que cada célula tenha novamente 3 ou menos?
flawr
2
Flawr @: Sim, isso seria correto. Veja a diferença entre t = 15 et = 16.
El'endia Starman 04/09/16
@LuisMendo A entrada é especificada como t positivo , então zero não é uma entrada válida.
Eric Tressler
11
É realmente necessário ter .células vazias? Podemos ter 0como uma célula vazia válida?
Andreï Kostyrka 5/09/16

Respostas:

56

R, 378 343 297 291 bytes

Como normalmente, o usuário fornece sua entrada via scan()(eu já usei a variável t, então vamos usar z), para que a segunda linha seja lançada separadamente e depois o resto:

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

Gera uma matriz que contém os valores de ana tgeração (0, 1, 2 ou 3).

Casos de teste:

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

Isso nos ajuda a que isso seja simétrico tanto vertical quanto horizontalmente, o que significa que o ponto mais à esquerda recebe uma altura de 4, o que significa que os pontos mais alto, mais à direita e mais baixos também são 4.

Ah, e eu disse que você pode fazer belas visualizações?

Após 1000 gotas:

Pilha de areia abeliana após 1000 passos

Após 50000 quedas (≈4 segundos):

Pilha de areia abeliana após 50000 etapas

Após 333333 quedas (± 15 minutos):

Pilha de areia abeliana após 100000 etapas

Você pode desenhá-lo também!

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

Isso levou 4 segundos para 10000 iterações, mas diminuiu consideravelmente para tamanhos de matriz maiores (por exemplo, alguns minutos para 100000 iterações). É por isso que fica tão lento (eu Taxa de crescimentoestimei a taxa de crescimento como em e obtive τ (i) ≈689 · i ^ 1,08; portanto, o tempo médio por um grão adicional até que toda a pilha de areia assente após ia etapa seja um pouco maior que um) , e o tempo total em função do número de grãos cresce um pouco mais lentamente do que quadraticamente (T (i) ≈0,028 * i ^ 1,74):

Iteração média até a pilha assentar

Tempo aproximado de computação

E agora com uma explicação completa:

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

Esta é a primeira vez na minha vida em que objetos em crescimento (como a <- c(a, 1)) funcionam muito mais rápido do que pré-alocar uma grande matriz vazia de valores e preenchê-la gradualmente com uma tonelada de zeros não utilizados.

Atualizar. Golfed 18 bytes, removendo arr.indem whichvirtude Billywob e substituindo rep(0,n)com e=numeric;e(n)em 5 casos, devido à JDL , e mais de 17 bytes, devido à JPL .

Atualização 2. Como a pilha de areia é abeliana, ela pode começar com uma pilha da altura desejada, então removi o loop redundante e ganhei um enorme aumento de produtividade!

Andreï Kostyrka
fonte
11
Entendo a sua coluna extra, os índices de linha que você está produzindo, mas acho que quero restringir a saída a ser apenas "a resposta" e nada mais. Ainda bem que você incluiu fotos.
Eric Tressler
11
Boa resposta Andreï! Você pode definitivamente jogar alguns bytes, por exemplo rep(), predefinindo , desde que você o use 6 vezes. Em segundo lugar, não acho que você precise escrever a arr.ind=Topção para a which()função. Basta usar which(...,T).
Billywob 5/09/16
11
Pode ser mais fácil definir n=numerice usar isso, pois n(k)há menos caracteres que r(0,k). Eu gosto das fotos embora.
JDL
11
Outra sugestão: 1%*%0há menos caracteres que array(0,c(1,1)). Além disso, o segundo argumento u <- cbindpode ser apenas 1, cbindestendendo-o para o tamanho do primeiro argumento por padrão.
JDL
11
@GregMartin Corrigido isso. Desculpe por isso; na minha primeira língua, usamos a palavra "eu" e nunca nos preocupamos com o sexo da pessoa em questão (como "um pequeno passo para um homem"); ainda assim, às vezes, em ocasiões muito raras, chamo um cachorro de “ela” ou “ele”, enquanto deveria ser “ele”, a menos que você seja o proprietário e realmente queira enfatizar o sexo do seu anumal ( apesar de não ser tão difícil distinguir um macho de uma fêmea).
Andreï Kostyrka 7/09/16
13

MATL , 55 53 48 43 42 bytes

Inspirado pela resposta de @ flawr .

Saída gráfica :

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

Experimente no MATL Online! . Demora cerca de 10 segundos para entrada 30. Pode ser necessário atualizar a página e pressionar "Executar" novamente se não funcionar.

Aqui está um exemplo de resultado para entrada 100:

insira a descrição da imagem aqui

Saída ASCII (43 bytes) :

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

Experimente online!

Explicação

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
Luis Mendo
fonte
3
Estou com ciúmes 1Y6.
flawr
11
@flawr Mas o seu ~mod(spiral(3),2)é :-) muito mais inteligente
Luis Mendo
11

Matlab, 160 156 148 bytes

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,'']

Primeiro, é criada uma matriz muito grande, com a nno meio em algum lugar. Então a cascata é calculada com uma convolução 2D muito conveniente. No final, o excesso é cortado e a coisa toda é convertida em uma string.

Exemplo de saída para t=100

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

Como sempre:

Convolução é a chave do sucesso.

flawr
fonte
v=any(z)em vez de v=find(sum(z))(estou usando isso na minha resposta). Além disso, em 2*~zvez de(z<1)*2
Luis Mendo
Meu computador congelou na entrada n=500... Ele estava sendo processado n=400por vários segundos. Estou fazendo algo errado?
Andreï Kostyrka 5/09/16
@ AndreïKostyrka Ele funciona para mim (Matlab R2015b)
Luis Mendo
11
@ AndreïKostyrka Para que uma entrada ndeste programa gere uma 3*n x 3*nmatriz, é necessário armazenar sobre 9*n^2números. Também é totalmente ineficiente, porque também temos uma iteração longa totalmente desnecessária de 1 a n. Mas, novamente, isso é código-golfe , tornar um programa eficiente é uma xícara de chá diferente.
flawr
@ AndreïKostyrka Você pode aumentar a eficiência da memória usando matrizes esparsas (segunda linha :) z=sparse(zeros(2*n+1))e alterando o loop for para while any(z(:)>3). Depois, você pode talvez também calcular o kernel de convolução apenas uma vez: kern = 1-mod(spiral(3),2).
flawr
9

Python 2, 195 +1 +24 = 220 217

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)]

saída para 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]])

há MUITO preenchimento e iterações desnecessários, usando ncomo um limite superior "suficientemente bom", mas n = 200 ainda é concluído em um segundo en = 500 em cerca de 12 segundos

destroçado

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

substituir return xpor imshow(x)adiciona um caractere e gera uma imagem interpolada feia; a adição imshow(x,'gray',None,1,'nearest')remove a interpolação tremida, elevando a saída às especificações

n = 100

DenDenDo
fonte
Eu recebo o seguinte erro quando eu executar o código: ImportError: No module named convolve2d. Alterar import scipy.signal.convolve2d as cpara from scipy.signal import convolve2d as cresolver o problema. Estou usando o scipy versão 0.16.1, preciso de uma versão mais antiga ou mais recente? Ou a questão é outra coisa?
Andrew Epstein
estranho, agora que você mencionou que não funciona mais para mim também. Eu provavelmente fiz isso logo na primeira vez em modo interativo, então encurtado e ignorou o erro, mas a função ficou na memória
DenDenDo
6

Perl, 157 147 bytes

Inclui +1 para -p

Execute com a contagem em STDIN, imprima o mapa usando 0123para 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$_}{
Ton Hospel
fonte
5

Python 3 2, 418 385 362 342 330 bytes

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))

Edit: salvou 6 bytes graças a @ Qwerp-Derp

Todo o crédito para @ Andreï Kostyrka, pois esta é uma tradução direta de seu código R para Python.

Andrew Epstein
fonte
Eu acho que você pode mover a atribuição de a,x,rpara os argumentos da função.
Loovjo 5/09/16
11
Joguei seu código com alguns bytes ... não é muito, mas será necessário. Você se importa se eu colocar uma edição na sua resposta e se eu alterar a versão do Python para Python 2?
clismique
@ Qwerp-Derp: Fique à vontade! Eu adoraria ver o que você fez.
Andrew Epstein
3

JavaScript, 418 416 406 400 393 bytes

Cria uma função anônima que exibe a saída no console.

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)">

hetzi
fonte
11
Aviso: pressionei 'executar' sem entrada e minha tela travou (loop infinito). Não seja tão bobo como eu era.
Roberton #
11
@Roberrrt Atualizei minha resposta para evitar isso.
hetzi 6/09/16
3

Nim, 294 caracteres

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]

Compilar e executar:

nim c -r sandbox.nim 1000

Notas:

  1. Consegui criar uma versão mais curta, que usa um tamanho fixo de tabela, mas editei-a em favor da dinâmica.
  2. Depois que o sandbox é calculado, xé calculado como o número de zero colunas no início da linha do meio.
  3. Para exibição, a tabela é cortada, excluindo xlinhas e colunas de cada extremidade.

atuação

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
Danny Kirchmeier
fonte
3

Scala, 274 bytes

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)
}

Uso:

scala sandpile.scala <iterations>

Eu não acho que haja muito o que explicar sobre este. Basicamente, apenas adiciona um grão de areia ao centro. Em seguida, verifica se é maior que 4, se for o caso, transborda e verifica se todos os vizinhos são maiores que 4, transbordam, etc. É bastante rápido.

Atuação:

  • t = 10000 72ms
  • t = 20000 167ms
  • t = 30000 419ms
  • t = 40000 659ms
  • t = 100000 3413ms
  • t = 1000000 cerca de 6 minutos
AmazingDreams
fonte
Meu programa sugere que, centrado em (0,0), a pilha de areia atinge primeiro um raio de 15 em t = 1552. Isso exigiria uma matriz 31x31 para armazenar (coordenadas -15 a 15 inclusive). Você tem certeza de que isso está correto através de t = 5000?
Eric Tressler
Não tenho certeza se isso está correto, embora eu ache que entendi a lógica? Eu recebo um índice de matriz fora de exceção limites em t> 5593
AmazingDreams
Quando eu incremento e depois verifico imediatamente se há derramamento, ele sai dos limites em t = 1552. Eu diria que é a implementação correta. Eu atualizei o código.
AmazingDreams
Seu desempenho pode ser superado apenas pela manipulação direta de array em C ou Fortran com otimização do compilador. Eu te invejo.
Andreï Kostyrka 6/09/16
@ AndreïKostyrka, sim, é aqui que brilha Scala! Minha saída não está de acordo com as especificações embora assim que eu teria que trabalhar nisso
AmazingDreams
2

J, 76 bytes

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

Eu defino um verbo pque preenche uma borda de zeros ao redor da entrada. O verbo principal recebe uma matriz como entrada. Em seguida, verifica a primeira linha em busca de qualquer pilha de areia que contenha 4 ou mais grãos. Se o fizer, ele gera a mesma matriz, exceto o uso acolchoado pe, caso contrário, realiza a convolução 2d para simular a queda de grãos. O verbo principal é repetido até a convergência usando o operador de energia ^:_.

Uso

   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

Demora cerca de 46 segundos para calcular o resultado para n = 50000, e o resultado pode ser exibido usando o viewmatcomplemento com um esquema de cores monocromático.

figura

milhas
fonte
2

C 229 (com muitos avisos)

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]));}
Jerry Jeremiah
fonte
Ok, eu desisto: por que sua matriz é 99 por 98?
precisa
@EricTressler Como não achei isso nos testes ?!
Jerry Jeremiah
1

PHP, 213 bytes

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++];

recursivamente cria a pilha $p, lembrando o tamanho $m; depois imprime com loops aninhados.
Corra com -r.

Titus
fonte