Haskell , 166 154 байт
(-12 байт завдяки Laikoni, (розуміння zip та списку замість zipWith та лямбда, кращий спосіб генерування першого рядка))
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)
Спробуйте в Інтернеті!
Пояснення:
Функція i#n
малює ASCII-трикутник висоти 2^n
після i
етапів ітерації.
Використовуване кодування внутрішньо кодує порожні позиції як 1
і повні позиції як 0
. Тому перший рядок трикутника кодується як [1,1,1..0..1,1,1]
з 2^n-1
одиницями з обох сторін нуля. Щоб створити цей список, ми починаємо зі списку x=1<$[2..2^n]
, тобто зі списком [2..2^n]
із усім, що відображається 1
. Потім ми будуємо повний список якx++0:x
Оператор k!p
(детальне пояснення нижче), даючи індекс рядка k
та відповідний, p
формує нескінченний список рядків, які слідують за цим p
. Ми викликаємо його 1
і описаною вище початковою лінією, щоб отримати весь трикутник, а потім беремо лише перші 2^n
рядки. Потім ми просто роздруковуємо кожен рядок, замінюючи 1
пробіл і 0
на M
(за допомогою доступу до списку "M "
в місці розташування 0
або 1
).
Оператор k!p
визначається наступним чином:
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]]
По-перше, ми генеруємо три версії p
: 1:p
яка p
з попередньо створеною 1
, p
сама, і tail p++[1]
яка є лише першим елементом p
, із 1
доданим. Потім ми зашифровуємо ці три списки, даючи нам фактично всі елементи p
з їх лівими та правими сусідами, як (l,m,r)
. Ми використовуємо розуміння списку, щоб потім обчислити відповідне значення у новому рядку:
m*l*r+(m*(l*r-l-r)+1)*0^mod k(2^(n-i))
Щоб зрозуміти цей вираз, нам потрібно усвідомити два основні випадки, які слід врахувати: Або ми просто розширюємо попередній рядок, або ми знаходимось у точці, де починається порожнє місце в трикутнику. У першому випадку ми маємо заповнене місце, якщо якесь із плям у мікрорайоні заповнене. Це можна обчислити як m*l*r
; якщо будь-який з цих трьох дорівнює нулю, то нове значення дорівнює нулю. Інший випадок трохи складніше. Тут нам в основному потрібне виявлення краю. У наступній таблиці наведено вісім можливих кварталів з отриманим значенням у новому рядку:
000 001 010 011 100 101 110 111
1 1 1 0 1 1 0 1
Проста формула для отримання цієї таблиці була б такою, 1-m*r*(1-l)-m*l*(1-r)
що спрощує m*(2*l*r-l-r)+1
. Тепер нам потрібно вибрати між цими двома випадками, саме тут ми використовуємо номер рядка k
. Якщо mod k (2^(n-i)) == 0
нам доведеться використовувати другий випадок, інакше ми використовуємо перший випадок. Отже, термін 0^(mod k(2^n-i))
- це 0
якщо ми маємо використовувати перший випадок і 1
якщо ми повинні використовувати другий випадок. Як результат, ми можемо використовувати
m*l*r+(m*(l*r-l-r)+1)*0^mod k(2^(n-i))
загалом - якщо ми використовуємо перший випадок, ми просто отримуємо m*l*r
, тоді як у другому випадку додається додатковий термін, що дає загальну суму m*(2*l*r-l-r)+1
.