Haskell , 166 154 byte
(-12 byte nhờ Laikoni, (zip và hiểu danh sách thay vì zipWith và lambda, cách tốt hơn để tạo dòng đầu tiên))
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)
Hãy thử trực tuyến!
Giải trình:
Hàm này i#n
vẽ một tam giác chiều cao ASCII 2^n
sau i
các bước lặp.
Mã hóa được sử dụng nội bộ mã hóa các vị trí trống như 1
và các vị trí đầy đủ như 0
. Do đó, dòng đầu tiên của tam giác được mã hóa như [1,1,1..0..1,1,1]
với 2^n-1
các dòng ở cả hai phía của số không. Để xây dựng danh sách này, chúng tôi bắt đầu với danh sách x=1<$[2..2^n]
, tức là danh sách [2..2^n]
với mọi thứ được ánh xạ tới 1
. Sau đó, chúng tôi xây dựng danh sách đầy đủ nhưx++0:x
Toán tử k!p
(giải thích chi tiết bên dưới), được đưa ra một chỉ mục dòng k
và tương ứng p
tạo ra một danh sách vô hạn các dòng tiếp theo p
. Chúng tôi gọi nó với 1
và dòng bắt đầu được mô tả ở trên để có được toàn bộ tam giác, và sau đó chỉ lấy các 2^n
dòng đầu tiên . Sau đó, chúng tôi chỉ cần in từng dòng, thay thế 1
bằng khoảng trắng và 0
bằng M
(bằng cách truy cập danh sách "M "
tại vị trí 0
hoặc 1
).
Toán tử k!p
được định nghĩa như sau:
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]]
Đầu tiên, chúng tôi tạo ra ba phiên bản p
: 1:p
đó là p
với một bản được đặt 1
trước, p
bản thân nó và tail p++[1]
tất cả mọi thứ trừ phần tử đầu tiên p
, với phần được 1
nối thêm. Sau đó, chúng tôi nén ba danh sách này, cung cấp cho chúng tôi hiệu quả tất cả các yếu tố p
với hàng xóm bên trái và bên phải của họ, như (l,m,r)
. Chúng tôi sử dụng một sự hiểu biết danh sách để sau đó tính toán giá trị tương ứng trong dòng mới:
m*l*r+(m*(l*r-l-r)+1)*0^mod k(2^(n-i))
Để hiểu biểu thức này, chúng ta cần nhận ra có hai trường hợp cơ bản cần xem xét: Hoặc là chúng ta chỉ cần mở rộng dòng trước hoặc chúng ta đang ở điểm bắt đầu một điểm trống trong tam giác. Trong trường hợp đầu tiên, chúng tôi có một vị trí đầy nếu bất kỳ điểm nào trong khu phố được lấp đầy. Điều này có thể được tính như m*l*r
; nếu bất kỳ một trong ba số này bằng 0, thì giá trị mới bằng không. Các trường hợp khác là một chút phức tạp hơn. Ở đây, về cơ bản chúng ta cần phát hiện cạnh. Bảng sau đây cung cấp tám vùng lân cận có thể có giá trị kết quả trong dòng mới:
000 001 010 011 100 101 110 111
1 1 1 0 1 1 0 1
Một công thức đơn giản để mang lại bảng này sẽ được 1-m*r*(1-l)-m*l*(1-r)
đơn giản hóa m*(2*l*r-l-r)+1
. Bây giờ chúng ta cần chọn giữa hai trường hợp này, đó là nơi chúng ta sử dụng số dòng k
. Nếu mod k (2^(n-i)) == 0
, chúng ta phải sử dụng trường hợp thứ hai, nếu không, chúng ta sử dụng trường hợp thứ nhất. 0^(mod k(2^n-i))
Do đó, thuật ngữ này là 0
nếu chúng ta phải sử dụng trường hợp thứ nhất và 1
nếu chúng ta phải sử dụng trường hợp thứ hai. Kết quả là chúng ta có thể sử dụng
m*l*r+(m*(l*r-l-r)+1)*0^mod k(2^(n-i))
tổng cộng - nếu chúng ta sử dụng trường hợp đầu tiên, chúng ta chỉ cần nhận được m*l*r
, trong khi trong trường hợp thứ hai, một thuật ngữ bổ sung được thêm vào, đưa ra tổng số lớn m*(2*l*r-l-r)+1
.