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.