Haskell , 166 154 byte
(-12 byte grazie a Laikoni, (comprensione di zip e list invece di zipWith e lambda, modo migliore di generare la prima riga))
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)
Provalo online!
Spiegazione:
La funzione i#n
disegna un triangolo ASCII di altezza 2^n
dopo i i
passaggi dell'iterazione.
La codifica utilizzata codifica internamente le posizioni vuote come 1
e le posizioni complete come 0
. Pertanto, la prima riga del triangolo è codificata come [1,1,1..0..1,1,1]
con 2^n-1
quelle su entrambi i lati dello zero. Per creare questo elenco, iniziamo con l'elenco x=1<$[2..2^n]
, ovvero l'elenco [2..2^n]
con tutto ciò a cui è mappato 1
. Quindi, creiamo l'elenco completo comex++0:x
L'operatore k!p
(spiegazione dettagliata di seguito), dato un indice di riga k
e un corrispondente p
genera un elenco infinito di righe che seguono p
. Lo invochiamo con 1
e la linea di partenza sopra descritta per ottenere l'intero triangolo, quindi prendiamo solo le prime 2^n
linee. Quindi, stampiamo semplicemente ogni riga, sostituendola 1
con spazio e 0
con M
(accedendo all'elenco "M "
nella posizione 0
o 1
).
L'operatore k!p
è definito come segue:
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]]
Innanzitutto, generiamo tre versioni di p
: 1:p
che è p
con un 1
anteposto, p
se stesso e tail p++[1]
che è tutto tranne il primo elemento di p
, con un 1
allegato. Quindi comprimiamo queste tre liste, dandoci effettivamente tutti gli elementi p
con i loro vicini sinistro e destro, come (l,m,r)
. Usiamo una comprensione dell'elenco per poi calcolare il valore corrispondente nella nuova riga:
m*l*r+(m*(l*r-l-r)+1)*0^mod k(2^(n-i))
Per comprendere questa espressione, dobbiamo renderci conto che ci sono due casi di base da considerare: o semplicemente espandiamo la linea precedente o siamo in un punto in cui inizia un punto vuoto nel triangolo. Nel primo caso, abbiamo uno spazio pieno se uno degli spazi del quartiere è pieno. Questo può essere calcolato come m*l*r
; se uno di questi tre è zero, il nuovo valore è zero. L'altro caso è un po 'più complicato. Qui, abbiamo sostanzialmente bisogno del rilevamento dei bordi. La tabella seguente mostra gli otto possibili quartieri con il valore risultante nella nuova riga:
000 001 010 011 100 101 110 111
1 1 1 0 1 1 0 1
Una formula semplice per produrre questa tabella sarebbe quella 1-m*r*(1-l)-m*l*(1-r)
che semplifica m*(2*l*r-l-r)+1
. Ora dobbiamo scegliere tra questi due casi, che è dove utilizziamo il numero di riga k
. Se mod k (2^(n-i)) == 0
, dobbiamo usare il secondo caso, altrimenti usiamo il primo caso. Il termine 0^(mod k(2^n-i))
quindi è 0
se dobbiamo usare il primo caso e 1
se dobbiamo usare il secondo caso. Di conseguenza, possiamo usare
m*l*r+(m*(l*r-l-r)+1)*0^mod k(2^(n-i))
in totale - se utilizziamo il primo caso, otteniamo semplicemente m*l*r
, mentre nel secondo caso viene aggiunto un termine aggiuntivo, che dà il totale complessivo di m*(2*l*r-l-r)+1
.