Haskell , 166 154 octets
(-12 octets grâce à Laikoni, (compréhension de zip et de liste au lieu de zipWith et lambda, meilleur moyen de générer la première ligne))
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)
Essayez-le en ligne!
Explication:
La fonction i#n
dessine un triangle ASCII de hauteur 2^n
après des i
étapes d'itération.
Le codage utilisé en interne code les positions vides en tant que 1
et les positions complètes en tant que 0
. Par conséquent, la première ligne du triangle est codée comme [1,1,1..0..1,1,1]
avec des 2^n-1
uns des deux côtés du zéro. Pour construire cette liste, nous commençons par la liste x=1<$[2..2^n]
, c'est-à-dire la liste [2..2^n]
avec tout ce qui est mappé 1
. Ensuite, nous construisons la liste complète en tant quex++0:x
L'opérateur k!p
(explication détaillée ci-dessous), à partir d'un index de ligne k
et d'un correspondant, p
génère une liste infinie de lignes qui suivent p
. Nous l'invoquons avec 1
la ligne de départ décrite ci-dessus pour obtenir le triangle entier, puis prenons uniquement les premières 2^n
lignes. Ensuite, nous imprimons simplement chaque ligne en remplaçant 1
par espace et 0
par M
(en accédant à la liste "M "
à l'emplacement 0
ou 1
).
Opérateur k!p
est défini comme suit:
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]]
Premièrement, nous générons trois versions de p
: 1:p
qui est p
avec un 1
préfixe, p
lui-même et tail p++[1]
qui est tout sauf le premier élément de p
, avec un 1
ajouté. Nous zippons ensuite ces trois listes, nous donnant effectivement tous les éléments de p
leurs voisins gauche et droit, comme (l,m,r)
. Nous utilisons une liste de compréhension pour calculer ensuite la valeur correspondante dans la nouvelle ligne:
m*l*r+(m*(l*r-l-r)+1)*0^mod k(2^(n-i))
Pour comprendre cette expression, nous devons comprendre qu'il existe deux cas fondamentaux à prendre en compte: soit nous élargissons simplement la ligne précédente, soit nous nous trouvons à un point où commence un point vide dans le triangle. Dans le premier cas, nous avons un point rempli si l’un des points du voisinage est rempli. Cela peut être calculé comme suit m*l*r
: si l'un de ces trois est zéro, alors la nouvelle valeur est zéro. L'autre cas est un peu plus compliqué. Ici, nous avons essentiellement besoin d'une détection de bord. Le tableau suivant donne les huit quartiers possibles avec la valeur résultante dans la nouvelle ligne:
000 001 010 011 100 101 110 111
1 1 1 0 1 1 0 1
Une formule simple pour obtenir ce tableau serait ce 1-m*r*(1-l)-m*l*(1-r)
qui simplifie m*(2*l*r-l-r)+1
. Nous devons maintenant choisir entre ces deux cas, c'est-à-dire où nous utilisons le numéro de ligne k
. Si mod k (2^(n-i)) == 0
, nous devons utiliser le second cas, sinon, nous utilisons le premier cas. Le terme 0^(mod k(2^n-i))
est donc 0
si nous devons utiliser le premier cas et 1
si nous devons utiliser le second cas. En conséquence, nous pouvons utiliser
m*l*r+(m*(l*r-l-r)+1)*0^mod k(2^(n-i))
au total - si nous utilisons le premier cas, nous obtenons simplement m*l*r
, alors que dans le second cas, un terme supplémentaire est ajouté, donnant le grand total de m*(2*l*r-l-r)+1
.