Haskell , 166 154 байта
(-12 байт, благодаря Laikoni, (понимание zip и списка вместо zipWith и lambda, лучший способ генерации первой строки))
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.