[Tous langages] Atelier de Noël : dessinons un sapin !

♪ Mon beau sapin, roi des forêts ♫

a marqué ce sujet comme résolu.

67 octets pour le code Ruby de @motet-a et le code demande la valeur de n. ^^

+3 -0

Sympa ce petit atelier ! :)

Comme je viens de découvrir les bases des scripts bash il y a quelques semaines, ça me permet d’avoir un petit entraînement. ^^ Du coup c’est vraiment niveau débutant, il n’y a rien d’original ou de particulier (mais ça fonctionne ! :D ).

Mon code prend en argument le nombre de lignes du sapin, et si rien n’est indiqué c’est 5 par défaut.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#!/bin/bash

if [ $# -lt 1 ]
then
    N=5
else
    N=$1
fi

nblanc=$(($N-1))

for line in `seq 1 $N` ; do
    for b in `seq 1 $nblanc` ; do
        echo -n ' '
    done
    for s in `seq 1 $((($line-1)*2+1))` ; do
        echo -n '*'
    done
    echo ''
    nblanc=$(($nblanc-1))
done

for b in `seq 1 $(($N-1))` ; do
    echo -n ' '
done
echo '#'
+0 -0

Une feinte encore inexplorée : https://jsfiddle.net/vf380Lgw/4/

<body style="text-align:center"> +

1
n=6;x='';for(i=0;i<n;i++)x+='*'.repeat(i)+'<br>';x+='#';document.body.innerHTML=x

[edit] ( https://jsfiddle.net/vf380Lgw/6 ) Correction suite à la remarque ci-dessous:

1
n=6;x='';for(i=0;i<=n;i++)x+='*'.repeat(i)+'<br>';x+='#';document.body.innerHTML=x

[edit^2] Mieux: https://jsfiddle.net/vf380Lgw/9/

1
2
3
4
n=6
x='<pre style="text-align:center">'
for(i=0;i<=n;)x+='*'.repeat(i++)+'\n'
document.body.innerHTML=x+'#'
+0 -0

@victor, bien tenté mais ton sapin ne respecte pas les contraintes. Ton ordre 6 n’a que 5 lignes de haut, et les lignes ne sont pas les bonnes.

Ton programme devrait donner :

1
2
3
4
5
6
7
     *
    ***
   *****
  *******
 *********
***********
     #

@tleb : bien tenté, mais ce cas est aussi hors spécifications : l’ordre doit être entier positif non-nul :)

Je n’ai pas vu de solution en brainfuck, et comme je n’ai rien de mieux à faire de mon premier samedi de vacances (sans doute pas le plus optimal):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
              >
             +++
            [<+++
           +++++++
          >-]<++>>+
         +++[<++++++
        ++++>-]<++>,>
       >++++[<++++++++
      ++>-]<++++++++[<-
     >-]<[->+>+<<]++++++
    ++++>>[[>+>+<<-]>>[<<
   +>>-]<-[<<<<<.>>>>>-]<<
  [->>+>+<<<]>>>[-<<<+>>>]<
 <[->>+>+<<<]>>>[-<<<+>>>]<[
<->-]<[->++<]>+[<<<<<.>>>>>-]
             <<<
             <.>>
    -]<-[-<<<.>>>]<<<+++.

avec quelques commentaires :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
>+++[<++++++++++>-]<++ on entre 32 (=espace) dans la case 0
>>++++[<++++++++++>-]<++ on entre 42 (=*) dans la case 1
>,>>++++[<++++++++++>-]<++++++++[<->-]< on entre n est dans la case 2 et on lui soustrait 48
[->+>+<<] on duplique n en case 3 et 4 la case 3 contiendra la constante
++++++++++ on stocke \n en 2
>> on avance jusqu'a la case 4
[ tant que la case 4 n'est pas nulle (on appelle k la valeur de la case 4; c'est l'etage du sapin)
  on va afficher k moins 1 espaces
  on duplique k en 5 et 6 en annulant 4 et on va en 6
  [>+>+<<-]>>
  on remet k en 4 en annulant 6 et on va en 5
  [<<+>>-]<
  on enleve 1 a 5
  -
  on affiche les espaces et on va en 3
  [<<<<<.>>>>>-]<<
  on va afficher 1 plus 2(n moins k) etoiles
  on copie n en 5 et 6 en annulant 3 et on va en 6
  [->>+>+<<<]>>>
  on remet n en 3 en annulant 6 et on va en 4
  [-<<<+>>>]<<
  on copie k en 6 et 7 en annulant 4 et on va en 7
  [->>+>+<<<]>>>
  on remet k en 4 et on va en 6 (on y a stocke la valeur n)
  [-<<<+>>>]<
  on enleve k a n dans la case 5 en annulant 6 et on va en 5
  [<->-]<
  on stocke 2(n moins k) dans la case 6 en annulant 5 et on va en 6
  [->++<]>
  on ajoute 1 a la case 6
  +
  on affiche les etoiles et on va en 4
  [<<<<<.>>>>>-]<<
  on affiche \n
  <<.>>
  on enleve 1 a 4
  -
]
on va afficher n moins 1 espaces
on va en 3 et on enleve 1 a n
<-
puis on affiche les espaces
[-<<<.>>>]
on va afficher un # pour le tronc
c'est la valeur 35 en ascii donc on ajoute 3 a la case 0 et on l'affiche
<<<+++.

En Kotlin 1.2 (cible JVM, nécessite une exécution en local et pas sur l’outil en ligne de JetBrains, nécessite Unicode 6.0) :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
fun main(args: Array<String>) {
    println(`🎄`(30))
}

class `🎄`(private val n: Int) {

    override fun toString(): String {
        return if (line() < n) "${" ".repeat(n - line() - 1)}${"*".repeat(line() * 2 + 1)}\n$this"
        else "${" ".repeat(n - 1)}#"
    }

    private fun line(): Int {
        return ((Exception().stackTrace.size - 2) / 3) - 1
    }
}

L’usage est très simple : il suffit de demander l’affichage du sapin de noël 🎄 avec le nombre de lignes en paramètre.

Ce sapin est un code unicode standard depuis Unicode 6.0 donc est un identificateur valide en Kotlin ; mais son point de code est au-delà de ce qui rentre dans UTF16, donc on doit le protéger par des ` (et accessoirement c’est invalide en Java).

Ensuite, on a toute la logique dans le .toString(). On y trouve un abus de variables à l’intérieur de chaines de caractères, et surtout cette méthode a été rendue récursive : le $this à la fin de la ligne 8 correspond à l’objet en cours, donc comme il est rendu dans une chaine de caractères on rappelle cette même méthode.

Enfin, pourquoi se faire chier à maintenir un compteur pour savoir quelle ligne est en cours de dessin, alors que puisqu’on a une fonction récursive, on peut déduire la ligne en cours d’affichage depuis la profondeur de la pile d’appel ?
C’est justement ce que fait la méthode de la ligne 12. Mais comme on a à ma connaissance aucun moyen direct de récupérer la profondeur de la pile d’appel, on crée une exception dont on récupère la pile d’appel donc sa taille.

Edit : Simplification de la méthode de la ligne 12.

J’avais envie de faire une version node pour rendre hommage au meilleur paquet disponible sur NPM.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
const leftPad = require('left-pad')

const sapin = (n) =>
  [...Array(n)]
    .map((_, i) => leftPad('*'.repeat((i + 1) * 2 - 1), n + 1 + i))
    .concat(leftPad('#', n + 1))
    .join('\n')


console.log(sapin(6))

C’est ma troisième contribution, j’arrête là. :lol:

+2 -0

Ya un paquet pour faire : ( x, y ) => ' '.repeat(y)+x) ? x’D

ache

Et le retrait de ce paquet de NPM a pété du jour au lendemain la moitié de l’écosystème de node l’année dernière. C’est ça le plus beau !

+3 -0

Alors pour cette version en Java, j’ai voulu faire simple. J’ai donc éliminé tout ce qui pourrait nuire à la bonne compréhension du code, à savoir :

  • Les notions compliquées de String ou pire, de StringBuffer|StringBuilder. Ou même de tableau de char. On affiche directement les caractères les uns après les autres.
  • Tout import explicite à une quelconque lib externe ou à une quelconque fonction non implicite de la JVM.
  • Tout ce qui ressemble à un objet, autre ce qui est strictement imposé en Java.
  • Toute forme de fonction.
  • Toutes variable intermédiaire inutiles.
  • Les boucles imbriquées — une seule, c’est déjà beaucoup.
  • Les variables avec un nom compliqué, donc composé de plusieurs caractères.
  • Tous les nombres dont la représentation est ambigüe selon la base utilisée – donc tout nombre autre que 0 ou 1.

À noter que j’avais une version avec un formatage en forme de sapin et un autre sans la variable intermédiaire de la ligne 4, mais là le code devenait difficilement lisible donc peu compréhensible.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class Sapin {

    public static void main(String[] args) {
        final int O = Integer.valueOf(args[0]);
        for (int l = 0; l < ((1 << 1) * O * (O + 1)); l++) {
            System.out.print((char) (((((1 << (1 << 1)) | 1) << (1 << 1)) | 1) << 1
                    & (l % ((1 << 1) * O) - (1 << 1) * O == -1
                        ? ~((1 << (1 << (1 << 1))) << 1)
                        : ~0)
                    & ((1 << 1) * O != l % ((1 << 1) * O) + 1
                            && (O == l / ((1 << 1) * O)
                                    || O > l % ((1 << 1) * O) + l / ((1 << 1) * O) + 1
                                    || l % ((1 << 1) * O) + 1 > O + l / ((1 << 1) * O))
                        ? ~((1 << (1 << 1)) << 1)
                        : ~0)
                    & ((1 << 1) * O != l % ((1 << 1) * O) + 1
                            && (O != l / ((1 << 1) * O)
                                    || O != l % ((1 << 1) * O) + 1)
                            && (O == l / ((1 << 1) * O)
                                    || O > l % ((1 << 1) * O) + l / ((1 << 1) * O) + 1
                                    || l % ((1 << 1) * O) + 1 > O + l / ((1 << 1) * O))
                        ? ~(1 << 1)
                        : ~0)
                    | (l / ((1 << 1) * O) == O
                            && l % ((1 << 1) * O) == O - 1
                        ? 1
                        : 0)
            ));
        }
    }
}

Vous pouvez vérifier, ce code fonctionne. Saurez-vous me dire comment ?

One-liner en python :

1
print('\n'.join(s.center(n*2) for s in [(i*2+1)*'*' for i in range(n)] + ['#']))

EDIT: et ma version la plus courte (rien d’original)

1
2
for i in range(n):print((n-i)*' '+(i*2+1)*'*')
print(n*' '+'#')

EDIT2: un second one-liner un peu plus chelou

1
print(*(s+'*'+s[::-1] for s in [(n*' '+n*'*')[i:n+i] for i in range(n)]),sep='\n',end='\n'+'#'.center(2*n))
+0 -0
1
for i in [(2*i+1)*"*"for i in range(n)]+['#']:print(i.center(2*n))

Car j’aime bien la version de yoch.

+1 -0

Vous pouvez vérifier, ce code fonctionne. Saurez-vous me dire comment ?

SpaceFox

D’abord la condition de la boucle : la ligne la plus large du sapin contient 2*O caractères ((1 << 1) * O) car il faut inclure le \n qui la conclut, et le sapin s’étend sur O+1 lignes. En fait, tu boucles sur un canevas rectangulaire.

Ensuite voyons les caractères possibles :

  • \n : 00001010
  • * : 00101010
  •  : 00100000
  • # : 00100011

La toute première ligne de la formule initialise le caractère à afficher à * (logique, c’est un des plus fréquents). Ensuite ça se corse. À un off-by-one près de ma part (lendemain de fête…), il faut le transformer en \n si l est un multiple de 2*O, en une espace si le reste de la division euclidienne de l par 2*O est inférieure à O - le résultat de cette division ou supérieure à O + le résultat de cette division, et en un # si l == O*O*2 + O, mais j’ai un petit peu la flemme de rentrer dans le détail des opérations bit à bit. :D

À ce que je vois c’est à peu près cette logique que suivent les 4 gros blocs que tu as identifié par l’indentation, la condition de l’espace (^^) étant la plus verbeuse car elle prend deux blocs.

Je vais de ce pas prendre quelque chose pour soulager la douleur de mon cerveau aviné en surchauffe. :p

+4 -0

Joli ! Tu y es presque à quelques détail près. Quelques précisions pour les curieux, avec un sapin d’ordre N (O dans le code).

Algorithmie

  • L’idée de base était d’afficher chaque caractère directement en fonction de sa position.
  • Pour simplifier les conditions, j’affiche un rectangle complet (donc des espaces après le sapin) de N * 2 caractères de large (saut de ligne complet) et N + 1 lignes.
  • La version d’origine me donnait le caractère à afficher en fonction de la coordonnée (x, y) du caractère.

Là je me suis rendu compte que les caractères à afficher ne différaient (dans leur représentation ASCII/ISO/UTF8) que de 4 bits (0, 1, 3 et 5). Donc, qu’il y avait moyen assez simplement au lieu de choisir le caractère de le construire en jouant avec les masques de bits.

Ce que fait le code :

  1. La ligne 6 définit le caractère * : 00001010, qui est le plus courant et surtout qui simplifie le plus les calculs suivants.
  2. Les lignes 7 à 9 passent le bit 5 à 0 si la coordonnée correspond à un saut de ligne.
  3. Les lignes 10 à 15 passent le bit 3 à 0 si la coordonnée correspond à une espace ou au tronc.
  4. Les lignes 16 à 23 passent le bit 1 à 0 si la coordonnée correspond à une espace.
  5. Les lignes 24 à 17 passent le bit 0 à 1 si la coordonnée correspond au tronc.

À noter que les étapes 3 et 4 sont complexifiées par le fait qu’il faut éviter les effets de bord, donc les conditions sont assez tordues.

Il y a peut-être moyen de simplifier les condition que j’ai mises, mais elles sont si tordues que Wolphram Alpha refuse de les interpréter (et donc de les simplifier) :D

À ne niveau, on obtient à peu près une double boucle for sur x et y avec ceci dedans :

1
2
3
4
5
6
System.out.print((char) (0b0010_1010
        & (x == 2 * n - 1 ? 0b1101_1111 : 0b1111_1111)
        & (2 * n != x + 1 && (n == y || n > x + y + 1 || x + 1 > n + y) ? 0b1111_0111 : 0b1111_1111)
        & (2 * n != x + 1 && (n != y || n != x + 1) && (n == y || n > x + y + 1 || x + 1 > n + y) ? 0b1111_1101 : 0b1111_1111)
        | (y == n && x == n - 1 ? 0b0000_0001 : 0b0000_0000)
));

Complexification

  • Remplacer la double boucle sur x et y par une simple boucle, en remplaçant les deux valeurs par la nouvelle.
  • Tenter les simplifications qui peuvent s’appliquer. Là encore, les formules des étapes 3 et 4 sont probablement sous-optimales mais trop compliquées pour être optimisées sans vraiment y réfléchir.
  • Remplacer tout nombre > 1 par une combinaison de 0, de 1, de décalage de bits à gauche << et de l’opérateur de négation ~ (presque jamais utilisé en Java)
  • Choisir des noms de variables qui ressemblent le plus possible à 0 et à 1.
  • Ajouter un petit laïus pour perdre le lecteur sur le fait que c’est censé être une version lisible :p
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte