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#nzeichnet 2^nnach iIterationsschritten ein ASCII-Dreieck der Höhe .
Die intern verwendete Codierung codiert leere Positionen als 1und volle Positionen als 0. Daher wird die erste Zeile des Dreiecks codiert , wie [1,1,1..0..1,1,1]mit 2^n-1denen 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 kund einem entsprechenden peine unendliche Liste der folgenden Zeilen p. Wir rufen es mit 1und der oben beschriebenen Startzeile auf, um das gesamte Dreieck zu erhalten, und nehmen dann nur die ersten 2^nZeilen. Dann drucken wir einfach jede Zeile aus und ersetzen sie 1durch Leerzeichen und 0mit M(indem wir auf die Liste "M "am Standort 0oder zugreifen 1).
Der Operator k!pist 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:pdie pmit einem 1vorangestellten, sich pselbst und tail p++[1]das alles andere als das erste Element von p, mit einem 1angehängten. Wir zippen dann diese drei Listen und geben uns effektiv alle Elemente pmit 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)) == 0wir den zweiten Fall verwenden müssen, verwenden wir den ersten Fall. Der Begriff 0^(mod k(2^n-i))lautet daher, 0ob wir den ersten Fall verwenden müssen und 1ob 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.