Haskell , 166 154 Bytes
(-12 Bytes dank Laikoni, (zip- und Listenverständnis statt zipWith und Lambda, bessere Möglichkeit, die erste Zeile zu generieren))
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)
Probieren Sie es online!
Erläuterung:
Die Funktion i#n
zeichnet 2^n
nach i
Iterationsschritten ein ASCII-Dreieck der Höhe .
Die intern verwendete Codierung codiert leere Positionen als 1
und volle Positionen als 0
. Daher wird die erste Zeile des Dreiecks codiert , wie [1,1,1..0..1,1,1]
mit 2^n-1
denen auf beiden Seiten der Null. Um diese Liste zu erstellen, beginnen wir mit der Liste x=1<$[2..2^n]
, dh der Liste, [2..2^n]
auf die alles abgebildet ist 1
. Dann erstellen wir die vollständige Liste alsx++0:x
Der Operator k!p
(ausführliche Erläuterung unten) erzeugt bei gegebenem Zeilenindex k
und einem entsprechenden p
eine unendliche Liste der folgenden Zeilen p
. Wir rufen es mit 1
und der oben beschriebenen Startzeile auf, um das gesamte Dreieck zu erhalten, und nehmen dann nur die ersten 2^n
Zeilen. Dann drucken wir einfach jede Zeile aus und ersetzen sie 1
durch Leerzeichen und 0
mit M
(indem wir auf die Liste "M "
am Standort 0
oder zugreifen 1
).
Der Operator k!p
ist wie folgt definiert:
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]]
Zuerst erzeugen wir drei Versionen von p
: 1:p
die p
mit einem 1
vorangestellten, sich p
selbst und tail p++[1]
das alles andere als das erste Element von p
, mit einem 1
angehängten. Wir zippen dann diese drei Listen und geben uns effektiv alle Elemente p
mit ihren linken und rechten Nachbarn wie (l,m,r)
. Wir verwenden ein Listenverständnis, um dann den entsprechenden Wert in der neuen Zeile zu berechnen:
m*l*r+(m*(l*r-l-r)+1)*0^mod k(2^(n-i))
Um diesen Ausdruck zu verstehen, müssen wir zwei grundlegende Fälle berücksichtigen: Entweder erweitern wir einfach die vorherige Zeile, oder wir befinden uns an einem Punkt, an dem eine leere Stelle im Dreieck beginnt. Im ersten Fall haben wir eine ausgefüllte Stelle, wenn eine der Stellen in der Nachbarschaft ausgefüllt ist. Dies kann wie folgt berechnet werden m*l*r
; Wenn einer dieser drei Werte Null ist, ist der neue Wert Null. Der andere Fall ist etwas kniffliger. Hier benötigen wir grundsätzlich eine Kantenerkennung. Die folgende Tabelle gibt die acht möglichen Nachbarschaften mit dem resultierenden Wert in der neuen Zeile an:
000 001 010 011 100 101 110 111
1 1 1 0 1 1 0 1
Eine einfache Formel, um diese Tabelle zu erhalten, wäre, 1-m*r*(1-l)-m*l*(1-r)
was vereinfacht m*(2*l*r-l-r)+1
. Jetzt müssen wir zwischen diesen beiden Fällen wählen, in denen wir die Zeilennummer verwenden k
. Wenn mod k (2^(n-i)) == 0
wir den zweiten Fall verwenden müssen, verwenden wir den ersten Fall. Der Begriff 0^(mod k(2^n-i))
lautet daher, 0
ob wir den ersten Fall verwenden müssen und 1
ob wir den zweiten Fall verwenden müssen. Als Ergebnis können wir verwenden
m*l*r+(m*(l*r-l-r)+1)*0^mod k(2^(n-i))
Insgesamt - wenn wir den ersten Fall verwenden, erhalten wir einfach m*l*r
, während im zweiten Fall ein zusätzlicher Term hinzugefügt wird, der die Gesamtsumme von ergibt m*(2*l*r-l-r)+1
.