Haskell , 166 154 bytes
(-12 bytes graças a Laikoni, (compreensão de zip e lista em vez de zipWith e lambda, melhor maneira de gerar a primeira linha))
i#n|let k!p=p:(k+1)![m*l*r+(m*(l*r-l-r)+1)*0^mod k(2^(n-i))|(l,m,r)<-zip3(1:p)p$tail p++[1]];x=1<$[2..2^n]=mapM(putStrLn.map("M "!!))$take(2^n)$1!(x++0:x)
Experimente online!
Explicação:
A função i#n
desenha um triângulo ASCII de altura 2^n
após i
etapas da iteração.
A codificação usada internamente codifica posições vazias como 1
e posições completas como 0
. Por conseguinte, a primeira linha do triângulo é codificado como [1,1,1..0..1,1,1]
com 2^n-1
aquelas de ambos os lados do zero. Para construir esta lista, começamos com a lista x=1<$[2..2^n]
, ou seja, a lista [2..2^n]
com tudo mapeado 1
. Em seguida, criamos a lista completa comox++0:x
O operador k!p
(explicação detalhada abaixo), dado um índice de linha k
e um correspondente, p
gera uma lista infinita de linhas a seguir p
. Nós o chamamos com 1
e a linha de partida descrita acima para obter o triângulo inteiro e, em seguida, pegar apenas as primeiras 2^n
linhas. Em seguida, simplesmente imprimimos cada linha, substituindo 1
por espaço e 0
por M
(acessando a lista "M "
no local 0
ou 1
).
Operador k!p
é definido da seguinte maneira:
k!p=p:(k+1)![m*l*r+(m*(l*r-l-r)+1)*0^mod k(2^(n-i))|(l,m,r)<-zip3(1:p)p$tail p++[1]]
Primeiro, geramos três versões de p
: 1:p
qual é p
com um 1
prefixo em p
si e tail p++[1]
que é tudo, exceto o primeiro elemento de p
, com um 1
anexo. Em seguida, compactamos essas três listas, fornecendo todos os elementos efetivamente de p
seus vizinhos esquerdo e direito, como (l,m,r)
. Usamos uma compreensão de lista para calcular o valor correspondente na nova linha:
m*l*r+(m*(l*r-l-r)+1)*0^mod k(2^(n-i))
Para entender essa expressão, precisamos entender que existem dois casos básicos a serem considerados: ou simplesmente expandimos a linha anterior ou estamos em um ponto em que um ponto vazio no triângulo começa. No primeiro caso, temos um local preenchido se algum dos locais do bairro estiver preenchido. Isso pode ser calculado como m*l*r
; se algum desses três for zero, o novo valor será zero. O outro caso é um pouco mais complicado. Aqui, precisamos basicamente da detecção de borda. A tabela a seguir fornece as oito vizinhanças possíveis com o valor resultante na nova linha:
000 001 010 011 100 101 110 111
1 1 1 0 1 1 0 1
Uma fórmula simples para gerar essa tabela seria a 1-m*r*(1-l)-m*l*(1-r)
que simplifica m*(2*l*r-l-r)+1
. Agora precisamos escolher entre esses dois casos, que é onde usamos o número da linha k
. Se mod k (2^(n-i)) == 0
precisarmos usar o segundo caso, caso contrário, usaremos o primeiro caso. Portanto, o termo 0^(mod k(2^n-i))
é 0
se tivermos que usar o primeiro caso e 1
se tivermos que usar o segundo caso. Como resultado, podemos usar
m*l*r+(m*(l*r-l-r)+1)*0^mod k(2^(n-i))
no total - se usarmos o primeiro caso, simplesmente obtemos m*l*r
, enquanto no segundo caso, um termo adicional é adicionado, fornecendo o total geral de m*(2*l*r-l-r)+1
.