Costruisci una catasta di sabbia


59

Una catasta di sabbia abeliana , per i nostri scopi, è una griglia infinita con coordinate intere, inizialmente prive di sabbia. Dopo ogni secondo, un granello di sabbia viene posto in (0,0). Ogni volta che una cella a griglia ha 4 o più granelli di sabbia, versa contemporaneamente un granello di sabbia a ciascuno dei suoi quattro vicini. I vicini di (x, y) sono (x-1, y), (x + 1, y), (x, y-1) e (x, y + 1).

Quando una cellula si rovescia, può causare la fuoriuscita dei suoi vicini. Alcuni fatti:

  • Questa cascata alla fine si fermerà.
  • L'ordine in cui le cellule si rovesciano è irrilevante; il risultato sarà lo stesso.

Esempio

Dopo 3 secondi, la griglia appare come

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

Dopo 4 secondi:

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

Dopo 15 secondi:

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

E dopo 16 secondi:

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

La sfida

Nel minor numero di byte possibile, scrivere una funzione che accetta un singolo numero intero positivo t e genera un'immagine della pila di sabbia dopo t secondi.

Ingresso

Un singolo numero intero positivo t , in qualsiasi formato tu scelga.

Produzione

Un'immagine della pila di sabbia dopo t secondi, usando i personaggi

 . 1 2 3

Modifica: usa quattro caratteri distinti che ti piacciono o disegna un'immagine. Se non stai utilizzando ".123" o "0123", specifica nella risposta il significato dei caratteri.

A differenza degli esempi, l'output dovrebbe contenere il numero minimo di righe e colonne necessarie per mostrare la parte diversa da zero della sandpile.

Cioè, per l'ingresso 3, l'uscita dovrebbe essere

 3

Per 4, l'output dovrebbe essere

 .1.
 1.1
 .1.

punteggio

Si applica il punteggio golf standard.

Regole

Non sono consentite funzioni di linguaggio o librerie che già sanno cos'è una sandpile.

Modifica: la sezione di output è stata modificata, la limitazione del set di caratteri è stata completamente eliminata. Usa quattro caratteri o colori distinti che ti piacciono.


2
Può l'ingresso t essere 0? Qual è il risultato allora?
Luis Mendo,

1
È corretto per un dato timestep possono verificarsi più cascate di seguito? Quindi in quel timestep le cascate continuano ad accadere fino a quando ogni cella è di nuovo 3 o meno?
flawr

2
@flawr: Sì, sarebbe corretto. Guarda la differenza tra t = 15 e t = 16.
El'endia Starman,

@LuisMendo L'input è specificato come t positivo , quindi zero non è un input valido.
Eric Tressler,

1
È DAVVERO necessario .per celle vuote? Possiamo avere 0come cella vuota valida?
Andreï Kostyrka,

Risposte:


56

R, 378 343 297 291 byte

Come al solito, l'utente fornisce il proprio input tramite scan()(ho già usato la variabile t, quindi prendiamo zinvece), quindi la seconda riga dovrebbe essere lanciata separatamente, e poi il 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

Emette un array che contiene i valori di aat tth generation (0, 1, 2 o 3).

Casi test:

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

Ci aiuta a rendere questa cosa simmetrica sia in senso verticale che orizzontale, il che significa che il punto più a sinistra ha un'altezza di 4, ciò significa che anche i punti più in alto, più a destra e più in basso sono 4.

Oh, e ho detto che puoi fare delle belle visualizzazioni?

Dopo 1000 gocce:

Sandpile abeliano dopo 1000 passi

Dopo 50000 gocce (≈4 secondi):

Catasta di sabbia abeliana dopo 50000 gradini

Dopo 333333 gocce (≈15 minuti):

Catasta di sabbia abeliana dopo 100000 gradini

Puoi anche disegnarlo!

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

Questa operazione ha richiesto 4 secondi per 10000 iterazioni ma rallenta considerevolmente per array di dimensioni maggiori (ad esempio un paio di minuti per 100000 iterazioni). Questo è il motivo per cui diventa così lento (ho stimato il tasso di crescita come in Tasso di crescitae ottenuto τ (i) ≈689 · i ^ 1,08, quindi il tempo medio per un grano aggiuntivo fino a quando l'intera catasta di sabbia si deposita dopo iil passaggio è leggermente più grande di uno) e il tempo totale in funzione del numero di granuli cresce un po 'più lentamente rispetto al quadratico (T (i) ≈0,028 * i ^ 1,74):

Iterazione media fino a quando la pila non si deposita

Tempo di calcolo approssimativo

E ora con una spiegazione 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

Questa è la prima volta nella mia vita in cui gli oggetti in crescita (come a <- c(a, 1)) funzionano molto più velocemente rispetto alla pre-allocazione di una grande matrice vuota per i valori e riempendola gradualmente con una tonnellata di zeri inutilizzati.

Aggiornare. Giocato a golf 18 byte rimuovendo arr.inda whichcausa di Billywob e sostituendo rep(0,n)con e=numeric;e(n)in 5 casi a causa di JDL , e altri 17 byte a causa di JDL .

Aggiornamento 2. Poiché la sandpile è Abeliana, potrebbe iniziare con una pila dell'altezza desiderata, quindi ho rimosso il loop ridondante e ho ottenuto un enorme aumento della produttività!


1
Ottengo il tuo punto in merito alla colonna aggiuntiva, agli indici di riga che stai producendo, ma penso che voglio limitare l'output a essere solo "la risposta" e niente di più. Sono contento che tu abbia incluso le foto, però.
Eric Tressler,

1
Bella risposta Andreï! Potresti sicuramente giocare a golf a pochi byte, ad esempio predefinendo rep(), dato che lo usi 6 volte. In secondo luogo, non penso che sia necessario scrivere l' arr.ind=Topzione per la which()funzione. Basta usare which(...,T).
Billywob,

1
Potrebbe essere più golfoso definirlo n=numerice usarlo invece, poiché n(k)è meno caratteri di r(0,k). Mi piacciono le foto però.
JDL,

1
Un altro suggerimento: 1%*%0è meno caratteri di array(0,c(1,1)). Anche il secondo argomento u <- cbindpuò essere solo 1, cbindper impostazione predefinita lo estenderà alla lunghezza del primo argomento.
JDL,

1
@GregMartin Risolto questo. Scusa per quello; nella mia prima lingua, usiamo la parola "sé" e non ci preoccupiamo mai del genere della persona in questione (come "un piccolo passo per un uomo"); tuttavia, a volte, in occasioni molto rare, chiamo un cane "lei" o un "lui", mentre dovrebbe essere "esso", a meno che tu non sia il proprietario e desideri davvero enfatizzare il sesso del tuo anumale ( nonostante il fatto che distinguere un maschio da una femmina non sia così difficile).
Andreï Kostyrka,

13

MATL , 55 53 48 43 42 byte

Ispirato dalla risposta di @ flawr .

Uscita grafica :

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

Provalo su MATL Online! . Ci vogliono circa 10 secondi per l'input 30. Potrebbe essere necessario aggiornare la pagina e premere nuovamente "Esegui" se non funziona.

Ecco un risultato di esempio per l'input 100:

inserisci qui la descrizione dell'immagine

Uscita ASCII (43 byte) :

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

Provalo online!

Spiegazione

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
Sono geloso di 1Y6.
flawr

1
@flawr Ma il tuo ~mod(spiral(3),2)è molto più intelligente :-)
Luis Mendo,

11

Matlab, 160 156 148 byte

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

Innanzitutto viene creata una matrice troppo grande, con nil mezzo da qualche parte. Quindi la cascata viene calcolata con una convoluzione 2D molto conveniente. Alla fine l'eccesso viene tagliato e il tutto viene convertito in una stringa.

Esempio di output per t=100

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

Come sempre:

La convoluzione è la chiave del successo.


v=any(z)invece di v=find(sum(z))(lo sto usando nella mia risposta). Inoltre, 2*~zinvece di(z<1)*2
Luis Mendo il

Il mio computer si è bloccato sull'input n=500... È stato elaborato n=400per diversi secondi. Sto facendo qualcosa di sbagliato?
Andreï Kostyrka,

@ AndreïKostyrka Funziona per me (Matlab R2015b)
Luis Mendo,

1
@ AndreïKostyrka Perché un input di nquesto programma genera una 3*n x 3*nmatrice, quindi è necessario memorizzare i 9*n^2numeri. Inoltre è totalmente inefficiente, perché abbiamo anche una lunga iterazione totalmente inutile da 1 a n. Ma anche in questo caso si tratta di code-golf , rendere efficiente un programma è una tazza di tè diversa.
flawr

@ AndreïKostyrka Puoi renderlo più efficiente in termini di memoria usando matrici sparse (seconda riga:) z=sparse(zeros(2*n+1))e cambiando il ciclo for in while any(z(:)>3). Poi si può forse anche calcolare il kernel di convoluzione solo una volta: 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)]

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

c'è MOLTA imbottitura e iterazioni non necessarie, usando ncome limite superiore "abbastanza buono", ma n = 200 ancora completato in un secondo e n = 500 in circa 12 secondi

ungolfed

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

la sostituzione return xcon imshow(x)aggiunge un carattere e genera un'immagine brutta interpolata, l'aggiunta imshow(x,'gray',None,1,'nearest')rimuove l'interpolazione sfocata portando l'output alle specifiche

n = 100


Ottengo il seguente errore quando si esegue il codice: ImportError: No module named convolve2d. Cambiando import scipy.signal.convolve2d as cper from scipy.signal import convolve2d as crisolvere il problema. Sto usando Scipy versione 0.16.1, ho bisogno di una versione precedente o più recente? O il problema è qualcos'altro?
Andrew Epstein,

strano, ora che dici che non funziona più neanche per me. Probabilmente l'ho fatto bene la prima volta in modalità interattiva, poi l'ho accorciato e ignorato l'errore, ma la funzione è rimasta in memoria
DenDenDo

6

Perl, 157 147 byte

Include +1 per -p

Esegui con il conteggio su STDIN, stampa la mappa usando 0123su 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

Python 3 2, 418 385 362 342 330 byte

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

Modifica: salvato 6 byte grazie a @ Qwerp-Derp

Tutto merito a @ Andreï Kostyrka, in quanto questa è una traduzione diretta del suo codice R in Python.


Penso che tu possa spostare l'assegnazione a,x,rnegli argomenti della funzione.
Loovjo,

1
Ho ridotto il codice di alcuni byte ... non è molto, ma dovrà farlo. Ti dispiace se ho inserito una modifica nella tua risposta e se cambio la versione di Python in Python 2?
clismique,

@ Qwerp-Derp: sentiti libero! Mi piacerebbe vedere cosa hai fatto.
Andrew Epstein,

3

JavaScript, 418 416 406 400 393 byte

Crea una funzione anonima che visualizza l'output sulla 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)">


1
Avvertenza: ho premuto 'run' senza input e il mio schermo si è bloccato (loop infinito). Non essere sciocco come me.
roberrrt-s,

1
@Roberrrt Ho aggiornato la mia risposta per evitarlo.
hetzi,

3

Nim, 294 caratteri

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]

Compila ed esegui:

nim c -r sandbox.nim 1000

Appunti:

  1. Sono stato in grado di trovare una versione più breve che utilizza una dimensione di tabella fissa, ma l'ho modificata a favore di quella dinamica.
  2. Una volta calcolato il sandbox, x viene calcolato come il numero di colonne zero all'inizio della riga centrale.
  3. Per la visualizzazione, la tabella viene suddivisa escludendo xrighe e colonne da ciascuna estremità.

Prestazione

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 byte

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>

Non credo ci sia molto da spiegare su questo. Fondamentalmente aggiunge solo un granello di sabbia al centro. Quindi controlla se è più grande di 4, in tal caso si rovescia e controlla tutti i vicini più grandi di 4, si rovescia, ecc. È abbastanza veloce.

Prestazione:

  • t = 10000 72 ms
  • t = 20000 167 ms
  • t = 30000 419 ms
  • t = 40000 659 ms
  • t = 100000 3413ms
  • t = 1000000 circa 6 minuti

Il mio programma suggerisce che, centrata su (0,0), la pila di sabbia colpisce per prima un raggio di 15 at = 1552. Ciò richiederebbe un array 31x31 da memorizzare (coordinate da -15 a 15 incluso). Sei sicuro che questo sia corretto attraverso t = 5000?
Eric Tressler,

Non sono sicuro che sia corretto, anche se penso di avere la logica giusta? Ottengo un indice dell'array senza limiti sull'eccezione t> 5593
AmazingDreams,

Quando incremento e poi controllo immediatamente la fuoriuscita, questo va fuori dai limiti at = 1552. Direi che è l'implementazione corretta. Ho aggiornato il codice.
AmazingDreams,

Le tue prestazioni possono essere battute solo dalla manipolazione diretta dell'array in C o Fortran con l'ottimizzazione del compilatore. Ti invidio.
Andreï Kostyrka,

@ AndreïKostyrka, sì, è qui che brilla la scala! La mia uscita non è conforme alle specifiche, quindi dovrei lavorarci su
AmazingDreams il

2

J, 76 byte

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

Definisco un verbo pche riempie un bordo di zeri attorno all'input. Il verbo principale accetta un array come input. Quindi controlla la prima riga per eventuali file di sabbia che contengono 4 o più grani. Se lo si fa, produce lo stesso array ad eccezione di quello imbottito p, e altrimenti esegue la convoluzione 2D per simulare i grani in caduta. Il verbo principale viene ripetuto fino alla convergenza usando l'operatore di potenza ^:_.

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

Sono necessari circa 46 secondi per calcolare il risultato per n = 50000 e il risultato può essere visualizzato utilizzando il componente viewmataggiuntivo con una combinazione di colori monocromatica.

figura


2

C 229 (con molti avvisi)

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

Ok, mi arrendo: perché il tuo array è 99 per 98?
Eric Tressler,

@EricTressler Come non l'ho trovato nei test ?!
Jerry Jeremiah,


1

PHP, 213 byte

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

crea ricorsivamente la pila $p, ricordando la dimensione in $m; quindi stampa con loop nidificati.
Corri con -r.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.