Построить песочницу

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
Может ли вход т быть 0? Каков выход?
Луис Мендо
1
Верно ли, что на заданном временном шаге может происходить несколько каскадов подряд? Таким образом, в этом временном шаге каскады продолжают происходить, пока каждая клетка снова не станет равной 3 или меньше?
flawr
2
@flawr: Да, это было бы правильно. Посмотрите на разницу между t = 15 и t = 16.
El'endia Starman
@LuisMendo Вход указан как положительный t , поэтому ноль не является допустимым.
Эрик Тресслер
1
Действительно ли это необходимо .для пустых ячеек? Можем ли мы иметь 0в качестве действительной пустой ячейки?
Андрей Костырка

Ответы:

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

Выводит массив, содержащий значения aat- tго поколения (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 ступеней

После 50000 падений (≈4 секунды):

Абелева песочница после 50000 шагов

После 333333 капель (≈15 минут):

Абелева песочница после 100000 шагов

Вы тоже можете это нарисовать!

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.indв whichсилу Billywob и замены rep(0,n)с e=numeric;e(n)в 5 случаях из - за JDL , а еще 17 байт за счет JDL .

Обновление 2. Поскольку песочница абелева, она может начинаться со стека желаемой высоты, поэтому я удалил лишнюю петлю и получил огромное повышение производительности!

Андрей Костырка
источник
1
Я понял вашу точку зрения о дополнительном столбце, индексах строк, которые вы выводите, но я думаю, что я хочу ограничить вывод только «ответом», и ничего более. Я рад, что вы включили фотографии, хотя.
Эрик Тресслер
1
Хороший ответ, Андрей! Вы можете определенно сыграть несколько байтов, например, предварительно определив rep(), учитывая, что вы используете его 6 раз. Во-вторых, я не думаю, что вам нужно выписывать arr.ind=Tопцию для which()функции. Просто используйте which(...,T).
Billywob
1
Это может быть лучше определить n=numericи использовать вместо этого, так n(k)как символов меньше, чем r(0,k). Мне нравятся картинки, хотя.
JDL
1
Еще одно предложение: 1%*%0меньше символов, чем array(0,c(1,1)). Кроме того, вторым аргументом u <- cbindможет быть только 1, cbindпо умолчанию он будет расширен до длины первого аргумента.
JDL
1
@GregMartin Исправлено. Простите за это; на моем родном языке мы используем слово «я» и никогда не беспокоимся о поле лица, о котором идет речь (например, «один маленький шаг для мужчины»); тем не менее, иногда, в очень редких случаях, я называю собаку «она» или «он», тогда как она должна быть «ею», если, например, вы не владелец, и вы действительно не хотите подчеркнуть пол своего аномального ( несмотря на то, что отличить мужчину от женщины не так уж и сложно).
Андрей Костырка,
13

MATL , 55 53 48 43 42 байта

Вдохновленный ответом @ flawr .

Графический вывод :

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

Попробуйте это в MATL Online! , Требуется около 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.
Flawr
1
@flawr Но ты ~mod(spiral(3),2)намного умнее :-)
Луис Мендо,
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то посередине. Затем вычисляется каскад с очень удобной двумерной сверткой. В конце лишнее обрезается, и все это превращается в строку.

Пример вывода для t=100

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

Как всегда:

Свертка - ключ к успеху.

flawr
источник
v=any(z)вместо v=find(sum(z))(я использую это в своем ответе). Также 2*~zвместо(z<1)*2
Луис Мендо
Мой компьютер завис на входе n=500... Он работал в n=400течение нескольких секунд. Я делаю что-то неправильно?
Андрей Костырка,
@ AndreïKostyrka Это работает для меня (Matlab R2015b)
Луис Мендо
1
@ AndreïKostyrka Для ввода nэтой программы генерируется 3*n x 3*nматрица, поэтому в ней необходимо хранить около 9*n^2цифр. Также это абсолютно неэффективно, потому что у нас также есть совершенно ненужная длинная итерация от 1 до n. Но опять же, это код-гольф , а эффективная программа - это другая чашка чая.
Flawr
@ AndreïKostyrka Вы можете сделать его более эффективным с помощью памяти, используя разреженные матрицы (вторая строка:) z=sparse(zeros(2*n+1))и изменив цикл for на while any(z(:)>3). Тогда вы можете также возможно вычислить ядро свертки только один раз: 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)]

выход для 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 секунд

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

замена return xна imshow(x)добавляет один символ и выводит уродливое интерполированное изображение, добавление imshow(x,'gray',None,1,'nearest')удаляет размытую интерполяцию, доводя вывод до спецификаций

п = 100

DenDenDo
источник
Я получаю следующее сообщение об ошибке , когда я запускаю свой код ImportError: No module named convolve2d. Изменение, import scipy.signal.convolve2d as cчтобы from scipy.signal import convolve2d as cрешить проблему. Я использую версию scipy 0.16.1, мне нужна старая или более новая версия? Или проблема в другом?
Эндрю Эпштейн
странно, теперь, когда вы упоминаете, что это больше не работает для меня. Я, вероятно, сделал это правильно в первый раз в интерактивном режиме, затем сократил его и проигнорировал ошибку, но функция осталась в памяти
DenDenDo
6

Perl, 157 147 байт

Включает +1 для -p

Запустить с подсчетом на STDIN, распечатать карту, используя 0123STDOUT:

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 байт

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

Редактировать: сохранено 6 байт благодаря @ Qwerp-Derp

Вся заслуга @ Andreï Kostyrka, так как это прямой перевод его кода R на Python.

Эндрю Эпштейн
источник
Я думаю, что вы можете перенести присваивание a,x,rв аргументы функции.
Loovjo
1
Я обработал твой код на несколько байтов ... это не так много, но это нужно сделать. Вы не возражаете, если я внесу изменения в ваш ответ и поменяю ли я версию Python на Python 2?
Климат
@ 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)">

hetzi
источник
1
Предупреждение: я нажал «запустить» без ввода, и мой экран завис (бесконечный цикл). Не будь таким глупым, как я.
roberrrt-s
1
@Roberrrt Я обновил свой ответ, чтобы предотвратить это.
Хетци
3

Ним, 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 419 мс
  • t = 40000 659 мс
  • t = 100000 3413мс
  • t = 1000000 около 6 минут
AmazingDreams
источник
Моя программа предполагает, что в центре (0,0) песочница сначала достигает радиуса 15 при t = 1552. Для этого потребуется массив 31x31 для хранения (координаты от -15 до 15 включительно). Вы уверены, что это правильно через t = 5000?
Эрик Тресслер,
Я не уверен, что это правильно, хотя я думаю, что понял логику правильно? Я получаю индекс массива за исключением границ при t> 5593
AmazingDreams
Когда я увеличиваю, а затем сразу проверяю, не пролился ли он, он выходит за пределы при t = 1552. Я бы сказал, что это правильная реализация. Я обновил код.
AmazingDreams
Ваша производительность может быть снижена только путем прямого манипулирования массивами в C или Fortran с оптимизацией компилятора. Я тебе завидую.
Андрей Костырка
@ AndreïKostyrka, да, здесь сияет скала! Мой вывод не соответствует спецификациям, поэтому мне придется поработать над этим
AmazingDreams
2

J, 76 байт

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

Я определяю глагол, pкоторый дополняет границу нулей вокруг ввода. Основной глагол принимает массив в качестве входных данных. Затем он проверяет первый ряд на наличие песочницы, которая содержит 4 или более зерна. Если это так, он выводит тот же массив за исключением заполнения p, а в противном случае он выполняет 2-мерную свертку для имитации падающих зерен. Основной глагол повторяется до сходимости с использованием степенного оператора ^:_.

использование

   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

С 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 на 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.

Titus
источник