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#ndesenha um triângulo ASCII de altura 2^napós ietapas da iteração.
A codificação usada internamente codifica posições vazias como 1e 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-1aquelas 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 ke um correspondente, pgera uma lista infinita de linhas a seguir p. Nós o chamamos com 1e a linha de partida descrita acima para obter o triângulo inteiro e, em seguida, pegar apenas as primeiras 2^nlinhas. Em seguida, simplesmente imprimimos cada linha, substituindo 1por espaço e 0por M(acessando a lista "M "no local 0ou 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:pqual é pcom um 1prefixo em psi e tail p++[1]que é tudo, exceto o primeiro elemento de p, com um 1anexo. Em seguida, compactamos essas três listas, fornecendo todos os elementos efetivamente de pseus 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)) == 0precisarmos usar o segundo caso, caso contrário, usaremos o primeiro caso. Portanto, o termo 0^(mod k(2^n-i))é 0se tivermos que usar o primeiro caso e 1se 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.