Haskell , 166 154 bajtów
(-12 bajtów dzięki Laikoni, (zapis zip i listy zamiast zipWith i lambda, lepszy sposób generowania pierwszego wiersza))
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)
Wypróbuj online!
Wyjaśnienie:
Funkcja i#nrysuje trójkąt ASCII wysokości 2^npo ikrokach iteracji.
Zastosowane kodowanie koduje wewnętrznie puste pozycje jako 1i pełne pozycje jako 0. W związku z tym, pierwsza linia trójkąta jest zakodowany jako [1,1,1..0..1,1,1]z 2^n-1tych po obu stronach zera. Aby zbudować tę listę, zaczynamy od listy x=1<$[2..2^n], tj. Listy [2..2^n]ze wszystkim, na co mapowane 1. Następnie tworzymy pełną listę jakox++0:x
Operator k!p(szczegółowe wyjaśnienie poniżej), podany indeks linii ki odpowiadająca mu pgeneruje nieskończoną listę kolejnych linii p. Wzywamy go za pomocą 1i linii początkowej opisanej powyżej, aby uzyskać cały trójkąt, a następnie wziąć tylko pierwsze 2^nlinie. Następnie po prostu drukujemy każdą linię, zastępując 1ją spacją i 0znakiem M(uzyskując dostęp do listy "M "w lokalizacji 0lub 1).
Operator k!pdefiniuje się w następujący sposób:
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]]
Po pierwsze, możemy wygenerować trzy wersje p: 1:pco jest pz 1poprzedzany, psam i tail p++[1]co jest wszystko, ale pierwszy element p, z 1dołączone. Następnie spakowujemy te trzy listy, dając nam skutecznie wszystkie elementy pz ich lewym i prawym sąsiadem, as (l,m,r). Używamy rozumienia listy, aby następnie obliczyć odpowiednią wartość w nowym wierszu:
m*l*r+(m*(l*r-l-r)+1)*0^mod k(2^(n-i))
Aby zrozumieć to wyrażenie, musimy zdać sobie sprawę z tego, że należy wziąć pod uwagę dwa podstawowe przypadki: albo po prostu rozszerzamy poprzednią linię, albo jesteśmy w punkcie, w którym zaczyna się puste miejsce w trójkącie. W pierwszym przypadku mamy wypełnione miejsce, jeśli któreś z miejsc w okolicy jest wypełnione. Można to obliczyć jako m*l*r; jeśli którykolwiek z tych trzech jest równy zero, to nowa wartość wynosi zero. Drugi przypadek jest nieco trudniejszy. W zasadzie potrzebujemy wykrywania krawędzi. Poniższa tabela podaje osiem możliwych sąsiedztw z wynikową wartością w nowym wierszu:
000 001 010 011 100 101 110 111
1 1 1 0 1 1 0 1
Prosta formuła umożliwiająca uzyskanie tej tabeli byłaby 1-m*r*(1-l)-m*l*(1-r)prostsza m*(2*l*r-l-r)+1. Teraz musimy wybrać między tymi dwoma przypadkami, w których używamy numeru linii k. Jeśli mod k (2^(n-i)) == 0musimy użyć drugiego przypadku, w przeciwnym razie użyjemy pierwszego przypadku. Terminem 0^(mod k(2^n-i))jest zatem, 0jeśli musimy użyć pierwszego przypadku i 1jeśli musimy użyć drugiego przypadku. W rezultacie możemy użyć
m*l*r+(m*(l*r-l-r)+1)*0^mod k(2^(n-i))
w sumie - jeśli użyjemy pierwszego przypadku, po prostu otrzymamy m*l*r, podczas gdy w drugim przypadku zostanie dodany dodatkowy termin, który daje sumę całkowitą m*(2*l*r-l-r)+1.