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#n
rysuje trójkąt ASCII wysokości 2^n
po i
krokach iteracji.
Zastosowane kodowanie koduje wewnętrznie puste pozycje jako 1
i 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-1
tych 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 k
i odpowiadająca mu p
generuje nieskończoną listę kolejnych linii p
. Wzywamy go za pomocą 1
i linii początkowej opisanej powyżej, aby uzyskać cały trójkąt, a następnie wziąć tylko pierwsze 2^n
linie. Następnie po prostu drukujemy każdą linię, zastępując 1
ją spacją i 0
znakiem M
(uzyskując dostęp do listy "M "
w lokalizacji 0
lub 1
).
Operator k!p
definiuje 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:p
co jest p
z 1
poprzedzany, p
sam i tail p++[1]
co jest wszystko, ale pierwszy element p
, z 1
dołączone. Następnie spakowujemy te trzy listy, dając nam skutecznie wszystkie elementy p
z 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)) == 0
musimy użyć drugiego przypadku, w przeciwnym razie użyjemy pierwszego przypadku. Terminem 0^(mod k(2^n-i))
jest zatem, 0
jeśli musimy użyć pierwszego przypadku i 1
jeś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
.