Encontre a pilha de areia de identidade

18

Esta pergunta é sobre pilhas de areia abelianas . Leia este desafio anterior e assista ao vídeo numberphile para saber mais.


Uma pilha de areia abeliana de tamanho n por n é uma grade contendo o número 0, 1, 2 e 3 (representando o número de grãos de areia). A adição de dois montes de areia funciona primeiro adicionando elemento por elemento e, em seguida, derrubando qualquer elemento acima de 3. A ordem na qual você tomba não importa, o resultado final é o mesmo. Quando uma célula tomba, seu número diminui em 4 e cada um de seus vizinhos diretos aumenta em 1. Isso pode causar uma reação em cadeia. Se uma célula estiver na borda da grade, todos os grãos que caem da grade enquanto caem desaparecem.

Por exemplo, estou adicionando duas pilhas de areia de 3 por 3 (dando uma reação em cadeia bastante extrema):

3 3 3   1 2 1   4 5 4    4 6 4    6 2 6    6 3 6    2 5 2    4 1 4    4 2 4    0 4 0    2 0 2    2 1 2
3 3 3 + 2 1 2 = 5 4 5 -> 6 0 6 -> 2 4 2 -> 3 0 3 -> 5 0 5 -> 1 4 1 -> 2 0 2 -> 4 0 4 -> 0 4 0 -> 1 0 1
3 3 3   1 2 1   4 5 4    4 6 4    6 2 6    6 3 6    2 5 2    4 1 4    4 2 4    0 4 0    2 0 2    2 1 2

Neste desafio, estamos interessados ​​em um subconjunto de todos os n possíveis n por n sandpiles. Esse subconjunto contém qualquer arquivo de areia que você pode obter adicionando um arquivo de areia arbitrário ao arquivo de areia all-3s n por n . Por exemplo, logo acima vimos que212 | 101 | 212 está no subconjunto, porque o conseguimos adicionando algo ao monte de areia all-3.

Agora esse subconjunto tem um elemento interessante: o elemento de identidade . Se você pegar esse elemento e adicioná-lo a qualquer outro elemento no subconjunto , a soma permanecerá inalterada. Em outras palavras, esse sandpile age como um zero desse subconjunto. Acontece que esse 212 | 101 | 212é o elemento zero para o subconjunto de 3 por 3. Por exemplo:

2 2 2   2 1 2   4 3 4    0 5 0    2 1 2    2 2 2
2 2 2 + 1 0 1 = 3 2 3 -> 5 2 5 -> 1 6 1 -> 2 2 2
2 2 2   2 1 2   4 3 4    0 5 0    2 1 2    2 2 2

Agora, este é o seu desafio: dado n , encontrar o elemento identidade do subconjunto do n por n grade . Faça a saída atribuindo uma cor exclusiva com contraste suficiente de sua escolha para cada uma 0, 1, 2, 3e produzindo uma imagem n por n. Seu código deve ser capaz de produzir a caixa de 50 por 50 em menos de um minuto em um PC moderno e razoável.


Por exemplo, o elemento de identidade 500 por 500:

Elemento de identidade 500 por 500

Aqui está azul = 3, verde = 2, vermelho = 1, branco = 0. Mas você não precisa usar esse esquema de cores na sua resposta.

orlp
fonte
2
Uma palavra de aviso aos concorrentes: descrevi qual é a solução, não como calculá-la. Seu código deve ser capaz de produzir a caixa de 50 por 50 em menos de um minuto; portanto, forçar brutalmente não é uma possibilidade. Existem algoritmos para resolver isso, e eu não estou dando a você. Isso é intencional. Sinto que muitos desafios apresentam alimentos pré-mastigados. Darei uma recompensa de +100 à primeira resposta que não trivialize o problema com um builtin (olhando para você, Mathematica), a meu critério.
orlp
2
Acho que a imagem da identidade 500x500 se beneficiaria em dizer qual número cada cor corresponde.
Xnor
O que constitui "contraste suficiente"?
Conor O'Brien
@ ConorO'Brien Qualquer conjunto de cores que seja suficientemente distinguível. Eu poderia torná-lo 100% objetivo com alguma medida de cor, mas acho que isso é um exagero. Não me importo se você usa vermelho, amarelo, verde, escala de cinza ou qualquer outra coisa, apenas não use 4 tons de cinza que estejam a 1% um do outro (como # 000000, # 000001, # 000002, # 000003).
orlp
ahem Eu acredito que esta questão agora é elegível para recompensa. Posso receber o bônus de +100? :)
JungHwan

Respostas:

2

Oitava, 120 113 bytes

function a=W(a);while nnz(b=a>3);a+=conv2(b,[t=[0 1 0];!t;t],'same')-b*4;end;end;@(n)imagesc(W((x=ones(n)*6)-W(x)))

Agradecemos a JungHwan Min por fornecer um link para o documento de referência em sua resposta do Mathematica.
Graças a Stewie Griffin me salvou 7 bytes[any(any(x)) -> nnz(x)]

Aqui, duas funções são usadas:

1 f.: para estabilização de uma matriz
2. Uma função anônima que assume ncomo entrada e mostra a matriz de identidade.

Experimente no rextester! para geração de uma matriz 50 * 50

Tempo decorrido para cálculo da matriz: 0.0844409 seconds.

Explicação:

Considere uma função f que estabiliza uma matriz, a tarefa de encontrar a identidade é simplesmente

f(ones(n)*6 - f(ones(n)*6).

isso ones(n)*6significa uma matriz * n de 6.

assim para n=3:

M = [6 6 6
     6 6 6
     6 6 6];

O resultado será f(M-f(M))

Para função de estabilização, convolução 2D usada para acelerar a tarefa; Em cada iteração, criamos uma matriz binária bcom o mesmo tamanho da matriz de entrada e a definimos como 1 se o elemento correspondente da matriz de entrada for> 3. Em seguida, aplicamos uma convolução 2D da matriz binária com a seguinte máscara

0 1 0
1 0 1
0 1 0

representando quatro vizinhos diretos.
O resultado da convolução é adicionado à matriz e 4 vezes a matriz binária subtraída dela.

O loop continuou até que todos os elementos da matriz fossem <= 3

Versão não destruída :

function a=stabilize(a)
    mask = [t=[0 1 0];!t;t];
    while any(any(b=a>3))
        a+=conv2(b,mask,'same')-b*4;
    end
end
n= 50;
M = ones(n)*6;
result = stabilize(M-stabilize(M));
imagesc(result);
rahnema1
fonte
8

Mathematica, 177 157 135 133 bytes

Colorize[f=BlockMap[⌊{l={0,1,0},1-l,l}#/4⌋~Total~2+#[[2,2]]~Mod~4&,#~ArrayPad~1,{3,3},1]&~FixedPoint~#&;k=Table[6,#,#];f[k-f@k]]&

Toma um número n . A saída é o sandpile de identidade. 0 é preto, 1 é cinza claro, 2 é magenta e 3 é cinza-azul.

Infelizmente, o Mathematica não tem um builtin para isso ...

Usa o algoritmo indicado no artigo de Scott Corry e David Perkinson .

O meu laptop de 5 anos leva 91,7 segundos para calcular o sandpile de identidade 50x50. Estou confiante de que um computador desktop moderno e razoável é 50% mais rápido. (Eu também tenho um código muito mais rápido no final).

Explicação

f= ...

Definir função f(a entrada é uma matriz de pilha de areia): uma função que ...

BlockMap[ ... ]~FixedPoint~#&

... repete a BlockMapoperação até que a saída não seja alterada. BlockMapOperação: ...


#~ArrayPad~1

... preencha a matriz de entrada com uma camada de 0 ...

{3,3},1

... particione-o em matrizes 3x3, com deslocamento 1 ...

⌊{l={0,1,0},1-l,l}#/4⌋~Total~2+#[[2,2]]~Mod~4&

... e para cada partição, adicione o número de grãos de areia derrubados na célula central e o valor da célula central mod 4.

ou seja, a saída de fé a versão estabilizada da entrada.


k=Table[6,#,#]

Definir kcomo um n por n matriz de 6s.

f[k-f@k]]

Calcule f (k - f (k)).

Colorize[ ... ]

Aplique cores ao resultado.

Versão mais rápida (142 bytes)

Colorize[r=RotateLeft;a=ArrayPad;f=a[b=#~a~1;b+r[g=⌊b/4⌋,s={0,1}]+g~r~-s+r[g,1-s]+r[g,s-1]-4g,-1]&~FixedPoint~#&;k=Table[6,#,#];f[k-f@k]]&

O mesmo código, mas usa a rotação da lista interna em vez de BlockMap. Calcula n = 50 em 4,0 segundos no meu laptop.

JungHwan Min
fonte
Considerando que você seguiu o espírito do limite de tempo (implementando um algoritmo real em vez de força bruta), e é muito plausível que um computador desktop poderoso seja 50% mais rápido, eu permitirei. Uma vez que implementa um algoritmo real sem uma trivialização integrada, isso se qualifica para o bônus de +100. Você terá que esperar por isso, pois ainda não posso começar uma recompensa.
orlp
Dito isto, implementar isso de maneira bastante trivial em Python (uma linguagem notoriamente lenta) leva apenas ~ 2s para n = 50. Talvez você possa acelerar um pouco?
orlp 16/01/19
@orlp Concluído, mas é mais longo que o código original. Devo fazer da versão mais rápida minha resposta principal ou posso colocá-la no final?
JungHwan Min
Como isso está bem, eu acho.
orlp
0

Python 3 + Numpy + PIL, 385 370 364 bytes

import numpy as q,PIL.Image as w
n=int(input())
z=n,n
def r(p):
 while len(p[p>3]):
  for x,y in q.ndindex(z):
   if p[x,y]>3:
    p[x,y]-=4;p[x-1,y]+=x>0;p[x,y-1]+=y>0
    if~-n>x:p[x+1,y]+=1
    if~-n>y:p[x,y+1]+=1
s=q.full(z,6)
t=s.copy()
r(t)
i=s-t
r(i)
w.fromarray(q.uint8(q.array(q.vectorize(lambda x:[x//1*65]*3,otypes=[object])(i).tolist()))).save('i.png')

Recebe entrada em STDIN. Produz a imagem em escala de cinza para i.png. Preto corresponde a 0, cinza escuro a 1, cinza claro a 2 e branco a 0.

Usa a fórmula I = R(S - R(S)), onde Ié o elemento de identidade, Sé a matriz preenchida com seis e Ré a função de redução.

Provavelmente eu poderia salvar alguns bytes mudando para Python 2 e executando from numpy import*, mas (1) não tenho o Numpy instalado no Python 2 e (2) o programa não estava terminando from numpy import*.

Ungolfed:

import numpy as np
from PIL import Image

# Compute the identity element

n = int(input('Size of the sandpile: '))

def reduce_pile(sandpile):
  while any(element >= 4 for element in np.nditer(sandpile)):
    for x, y in np.ndindex((n, n)):
      if sandpile[x, y] >= 4:
        sandpile[x, y] -= 4
        if x > 0: sandpile[x - 1, y] += 1
        if y > 0: sandpile[x, y - 1] += 1
        if x < n - 1: sandpile[x + 1, y] += 1
        if y < n - 1: sandpile[x, y + 1] += 1

s = np.full((n, n), 6, dtype=np.int32)
s_prime = np.copy(s)

reduce_pile(s_prime)

identity = s - s_prime
reduce_pile(identity)

# Output it to identity.png as an image

colours = [[255, 255, 255], [255, 0, 0], [0, 255, 0], [0, 0, 255]]
img_array = np.vectorize(lambda x: colours[x], otypes=[object])(identity)
img_array = np.array(img_array.tolist(), dtype=np.uint8)

img = Image.fromarray(img_array)
img.save('identity.png')
Cobre
fonte
Você pode salvar bytes usando scipyou matplotlibpara exibir os dados em vez de gerar uma imagem explicitamente com o PIL.
orlp