L'arbre de noël

Noël arrive (dans plus ou moins longtemps c'est vrai), ce sera le temps des c adeaux et des sapins de Noël dans tous les magasins. :)

Comme exercice, je vous propose de dessiner un Arbre de Noël dans la console.

Nous allons commencer par la version la plus simple puis ajouter des fonctionnalités au fur et à mesure.

Pour démarrer, commençons par dessiner la moitié d'un Arbre de Noël :

print("*")
print("**")
print("***")
print("*")
print("**")
print("***")
print("****")
print("*")
print("**")
print("***")
print("****")
print("*****")
print("******")
*
**
***
*
**
***
****
*
**
***
****
*****
******

C'est pas si mal, mais nous avons du taper beaucoup de choses. Et que se passe-t-il si je veux un arbre plus petit ? Ou un plus grand, composé de centaines d'étoiles pour l'imprimer sur un poster géant au format A0 ? Oui ça fait certainement beaucoup trop de caractères à taper, quand bien même on multiplierait les caractères par centaines ("" 100, et ainsi de suite). Ça ressemble au genre de tâche qu'on confierait volontiers à un programme ça, non ?

Les listes et les boucles for

Les boucles sont faites exactement pour ce genre d'actions répétitives. Pour rester dans l'atmosphère de Noël, imaginez un instant que vous êtes le Père Noël et que vous devez distribuer tous les cadeaux.

Comme vous le savez, les lutins ont une liste précise des enfants sages qui méritent un cadeau. La solution la plus simple pour garantir qu'un enfant ne soit pas oublié serait de prendre la liste et d'aller distribuer les cadeaux, dans l'ordre.

Outre les aspects physiques de la tâche, la procédure de distribution des cadeaux pourrait ressembler à cela:

Disons que la liste des enfants sages, contient la liste des enfants qui méritent un cadeau.

Pour chaque enfant (alias `child`), qui se trouve dans la liste des enfants sages:
    Distribuer un cadeau à cet enfant

La disposition du texte ci-dessus n'est pas une erreur, c'est en fait un programme Python déguisé:

children = children_who_deserve_gifts()

for child in children:
    deliver_gift(child)
    print("Cadeau distribué à :", child)
print("Tous les enfants sages ont reçus un cadeau")

La plupart des choses doivent vous sembler familières. On appelle deux fonctions :

  • children_who_deserve_gifts() et deliver_gift(), leur fonctionnement interne est uniquement connu du Père Noël.
  • Le résultats de la première peut être enregistré dans la variable children, afin de se rappeler par la suite à quoi corresponds cette valeur.

Le nouvel élément, c'est la boucle elle-même, qui consiste en :

  • Sur la première ligne
    • Le mot clé for,
    • Le nom du prochain élément de la liste,
    • Le mot clé in,
    • Une liste de valeur ou une variable qui y fait référence.
  • Les instructions indentées à effectuer pour chaque valeur de la liste (comme dans le cas de if).

Attendez, nous n'avons encore rien dit à propos des listes, mais rassurez-vous, le concept de liste en Python est très proche du concept de liste dans la vie de tous les jours. Nous pouvons simplement nous représenter une liste en Python comme nous nous représentons n'importe quelle autre liste le reste du temps (liste de courses, liste d'invités, résultats d'examens, etc...) écrite sur un papier et numérotée.

Commençons par une liste vide :

>>> L = []
>>> L
[]

Quand nous le souhaitons, nous pouvons demander le nombre d'éléments qui se trouvent dans notre liste en utilisant la fonction len().

>>> len(L)
0

Essayons avec une autre liste (qui peut avoir le même nom ou pas) :

>>> L = ["Yara", "Pierre", "Amel"]
>>> len(L)
3

Comme pour le cas des tuples, les éléments consécutifs d'une liste sont séparés par des virgules. À la différence des tuples, les crochets sont obligatoires.

Pour récupérer la valeur d'un élément d'une position particulière de la liste on se sert de sa position dans la liste (en se souvenant que les index des positions commencent à 0) :

>>> L[0]
'Yara'
>>> L[1]
'Pierre'
>>> L[2]
'Amel'
>>> L[3]
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
IndexError: list index out of range

Pour L[3] on obtient une exception de type IndexError qui nous indique que l'élément d'indice 3 n'existe pas.

En python, les listes sont dites dynamiques. On peut ajouter ou supprimer des éléments.

>>> L = ["Yara", "Pierre", "Amel"]
>>> L.append("Rémi")
>>> print(L)
['Yara', 'Pierre', 'Amel', 'Rémi']
>>> L.pop()
'Rémi'
>>> print(L)
['Yara', 'Pierre', 'Amel']
>>> L.remove("Pierre")
>>> print(L)
['Yara', 'Amel']

append(), pop(), et remove() sont des fonctions qui agissent sur la liste. On les appelles des méthodes, mais nous verrons ça plus précisément au sujet des objets et des classes.

La boucle for que nous avons vu tout à l'heure, va nous servir pour exécuter une instruction sur chaque élément de la liste. Elle permet de parcourir la liste.

>>> for name in L:
...     print("Nom :", name)
...
Nom : Yara
Nom : Pierre
Nom : Amel

En passant, nous pouvons ainsi afficher la première moitié de notre Arbre de Noël :

>>> lst = [1, 2, 3]
>>> for n in lst:
...     print("*" * n)
...
*
**
***

Malheureusement, nous devons encore écrire le contenu de la liste. Ce problème peut-être résolu à l'aide de la fonction range(). Vous pouvez entrer help(range) pour apprendre à l'utiliser (touche q pour sortir) ou regardez ces exemples :

>>> list(range(2, 5, 1))
[2, 3, 4]
>>> list(range(1, 11, 2))
[1, 3, 5, 7, 9]
>>> list(range(1, 11))
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> list(range(1, 2))
[1]
>>> list(range(2))
[0, 1]

La fonction range() ne crée pas directement une liste, mais retourne un générateur. Les générateurs génèrent les éléments un à un, ce qui permet de ne pas avoir à stocker l'ensemble des valeurs de la liste dans la mémoire de l'ordinateur.

Pour obtenir une liste à partir d'un générateur, on utilise la fonction list() (en fait il s'agit d'une classe mais nous verrons ça plus tard). Si on oublie l'appel à list(), le résultat ressemblera à ça :

>>> range(1, 4)
range(1, 4)

La fonction range() a trois formes. La plus simple, qui est la plus utilisée, permet de générer une séquence de nombres de 0 à un nombre donné. Les autres formes vous permettent de spécifier le chiffre de départ et le pas d'un nombre à l'autre de la séquence. La séquence créée n'inclut jamais la borne supérieure.

Affichons un Arbre de Noël plus grand :

>>> lst = list(range(1, 11))
>>> lst
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> for i in lst:
...     print("*" * i)
*
**
***
****
*****
******
*******
********
*********
**********

range() nous a épargné beaucoup de temps, on peut en gagner encore plus si on ne nomme pas la liste:

>>> for i in list(range(1, 5)):
...     print(i * "@")
@
@@
@@@
@@@@

Lorsqu'on utilise l'instruction for, on n'a pas besoin d'utiliser la fonction list(). for sait gérer le générateur retourné par range(). Ce qui nous permet de simplifier notre programme encore plus.

>>> for i in range(1, 5):
...     print(i * "@")
@
@@
@@@
@@@@

Rien ne nous empêche de créer une boucle dans une autre boucle, essayons ! Simplement rappelez-vous d'utiliser l'indentation appropriée et d'utiliser des noms différents, par exemple i et j, (ou mieux un nom en rapport avec le contenu de la liste) pour les éléments de chaque liste :

>>> for column in range(1, 3):
...    for line in range(11, 14):
...        print(column, line)
1 11
1 12
1 13
2 11
2 12
2 13

Nous avons une boucle intérieure allant de 11 à 13 (n'oubliez pas que, 14 n'est pas incluse lorsqu'on utilise range()), inclue dans une boucle extérieure qui elle va de 1 à 2 (idem, 3 n'est pas inclue).

Comme vous pouvez le voir les éléments de la boucle intérieure sont affichés deux fois, une fois pour chaque itération de la boucle extérieure.

En utilisant cette technique, on peut répéter les éléments de notre Arbre de Noël :

>>> for etages in range(3): # répéter 3 fois
...    for taille in range(1, 4):
...        print(taille * "*")
*
**
***
*
**
***
*
**
***

Avant d'aller plus loin, créez le fichier noel.py avec ce programme et essayez de le modifier afin que pour chaque itération de la boucle extérieure la boucle intérieure soit exécutée une fois de plus. (Que pour chaque étage on ait une branche de plus).

Vous devriez obtenir le résultat de notre demi Arbre de Noël décrit en début de chapitre. Par exemple :

*
**
*
**
***
*
**
***
****
*
**
***
****
*****
*
**
***
****
*****
******

Correction :

La principale modification concerne la boucle intérieure dont la borne supérieur doit changer à chaque nouvelle itération de la boucle extérieure.

On notera l'opérateur d'incrémentation += qui permet d'ajouter une valeur à la valeur déjà contenu dans une variable :

>>> i = 1
>>> print(i)
1
>>> i += 1
>>> print(i)
2
>>> i += 1
>>> print(i)
3

Solution :

taille_max = 2
for etages in range(5):
    taille_max += 1
    for taille in range(1, taille_max):
        print(taille * "*")

Les fonctions

Nous avons déjà pu voir comment les fonctions résolvent nombre de nos problèmes. Par contre elle ne les résolvent pas tous, ou du moins pas exactement de la manière dont nous aimerions les résoudre.

Parfois, et même assez souvent nous devons résoudre nous-mêmes un problème. Ce serait donc assez cool de pouvoir créer des fonctions qui le fassent pour nous.

Voici comment nous pouvons faire en Python:

>>> def print_triangle(n):
...     """ Affiche un triangle avec n lignes """
...     for size in range(1, n + 1):
...         print(size * "*")
...
>>> print_triangle(3)
*
**
***
>>> print_triangle(5)
*
**
***
****
*****

Regardons de plus près la fonction print_triangle():

def print_triangle(n):
    """ Affiche un triangle avec n lignes """
    for size in range(1, n + 1):
        print(size * "*")

La définition d'une fonction commence toujours avec l'instruction def. Ensuite on donne un nom à la fonction. Entre les parenthèses, on indique quels sont les noms des arguments passés à la fonction lorsqu'elle est appelée. Les lignes suivantes définissent les instructions à exécuter lors de l'utilisation de la fonction.

Comme vu dans l'exemple, les instructions peuvent utiliser les variables passées en arguments. Le principe opératoire est le suivant, si on créé une fonction avec trois arguments :

>>> def foo(a, b, c):
...     print("FOO", a, b, c)

Lorsque vous appelez cette nouvelle fonction, vous devez spécifier une valeur pour chacun des arguments. De la même manière que ce que nous faisions pour appeler les fonctions précédentes :

>>> foo(1, "Ala", 2 + 3 + 4)
FOO 1 Ala 9
>>> x = 42
>>> foo(x, x + 1, x + 2)
FOO 42 43 44

On notera qu'un argument est simplement un alias, si on modifie la valeur liée à cet alias pour une autre valeur, les variables initiales ne sont pas modifiés, c'est la même chose pour les arguments :

>>> def plus_five(n):
...     """ ajoute 5 à n """
...     n = n + 5
...     print(n)
>>> x = 43
>>> plus_five(x)
48
>>> x
43

ça fonctionne comme pour les variables que nous avons vu précédement. Il y a seulement deux différences :

  1. Premièrement, les variables correspondants aux arguments d'une fonction sont définies à chaque appel de la fonction. Python attache la valeur de la variable passée comme argument à la variable qu'il vient de créer spécifiquement pour l'appel de cette fonction.

  2. Deuxièmement, les variables correspondants aux arguments ne sont pas utilisable à l'extérieur de la fonction car ils sont créé lors de l'appel de la fonction et oublié à la fin de celle-ci. C'est pourquoi, si vous essayez d'accéder à la valeur de n que nous avons définie dans notre fonction plus_five(), à l'extérieur du code de la fonction Python vous dit qu'elle n'est pas définie :

>>> n
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'n' is not defined

C'est comme ça notre cher Python fait le ménage à la fin d'un appel de fonction. On parle alors de portée d'une variable ou de variable interne à la fonction.

Mais rappelez-vous, nous avons vu une instruction return qui nous permet de récupérer ce qui s'est passé dans la fonction. C'est une instruction spécifique qui ne fonctionne qu'au sein d'une fonction.

Pour finir, comme dernier exemple de fonction, voici la solution au problème posé à la fin du chapitre précédent en utilisant une fonction :

def print_triangle(n):
    """ Affiche un triangle avec n lignes """
    for size in range(1, n + 1):
        print(size * "*")

for i in range(2, 5):
    print_triangle(i)

Ce qui donne à l'exécution :

*
**
*
**
***
*
**
***
****

Un Arbre de Noël entier

Le chapitre précédent était principalement de la théorie. Utilisons nos nouvelles connaissances pour terminer notre programme et afficher notre Arbre de Noël.

Voici à quoi ressemble notre programme actuel:

def print_triangle(n):
    """ Affiche un triangle avec n lignes """
    for size in range(1, n+1):
        print(size * "*")

for i in range(2, 5):
    print_triangle(i)

Comment pouvons-nous améliorer la fonction print_triangle(), pour afficher un Arbre de Noël entier et non juste la moitié ?

Tout d'abord, essayons de déterminer le résultat attendu en fonction de la valeur de l'argument n. Il parait naturel que n soit la largeur. Ainsi pour n = 5 on s'attendrait à:

  *
 ***
*****

Il est intéressant de noter que chaque ligne possède deux étoiles de plus que la ligne précédente. Nous pouvons donc utiliser le troisième argument de range():

def print_segment(n):
    """ affiche un segment de l'arbre avec maximum n étoiles """
    for size in range(1, n + 1, 2):
        print(size * "*")

print_segment(5)
*
***
*****

Ce n'est pas exactement ce à quoi on s'attendait, il y a effectivement le bon nombre d'étoiles mais on souhaiterait qu'elles soient alignées au centre.

La fonction unicode.center() peut nous aider. Elle s'applique à une chaine de caractères. Voyons comment elle fonctionne :

>>> "Bonjour".center(10)
' Bonjour  '
>>> "**".center(4)
' ** '

Cette fois la chaine de caractères est centrée ! Modifions donc notre fonction :

def print_segment(n):
    """ affiche un segment de l'arbre avec maximum n étoiles """
    for size in range(1, n + 1, 2):
        print((size * "*").center(n))

print_segment(5)
  *
 ***
*****

Cependant, un nouveau problème apparait :

def print_segment(n):
    for size in range(1, n + 1, 2):
        print((size * "*").center(n))

for i in range(3, 8, 2):
    print_segment(i)
 *
***
  *
 ***
*****
   *
  ***
 *****
*******

Si nous avions un moyen de connaitre à l'avance la taille du segment le plus grand, nous pourrions ajouter un argument supplémentaire à print_segment(), pour faire le centrage sur cette largeur. Voici une solution en combinant toute les connaissances acquises :

def print_segment(segment_size, total_width):
    """
    affiche un segment sur une largeur de total_width avec
    au plus segment_size étoiles

    args :
        segment_size: maximum d'étoiles
        total_width: largeur du segment
    """
    for line_size in range(1, segment_size + 1, 2):
        print((line_size * "*").center(total_width))

def print_tree(size):
    """ affiche un arbre de noel de taille size """
    for segment_size in range(3, size + 1, 2):
        print_segment(segment_size, size)

print("Choisissez la taille de votre Arbre de Noël :")
tree_size = int(input())
print_tree(tree_size)
Choisissez la taille de votre Arbre de Noël :
7
   *
  ***
   *
  ***
 *****
   *
  ***
 *****
*******

La boucle while

Nous avons vu la boucle for permettant de répéter une opération un certains nombre de fois en parcourant une liste. Il existe une autre façon de répéter une action en python, il s'agit de la boucle while. En français, on dirait :

Tant que ma condition est vrai,
    opérations à effectuer

Ce qui se traduit en python par :

while condition:
    do_something()

Par exemple, nous allons demander 4 nombres entre 1 et 100 à l'utilisateur que nous enregistrerons dans une liste :

# compteur des nombres
n = 0
# listes pour enregistrer les valeurs
liste = list()

print("Donner moi 4 nombres entre 1 et 100:")
while n < 4:
    valeur = int(input("nombre : "))
    liste.append(valeur)
    n += 1
print("liste : ", liste)

Résultat :

Donner moi 4 nombres entre 1 et 100:
nombre : 10
nombre : 88
nombre : 17
nombre : 64
liste :  [10, 88, 17, 64]

Deux éléments manquent cruellement à ce petit programme :

  • On n'a pas vérifié si les nombres étaient compris entre 1 et 100
  • La taille de la liste (4) est imposée

Compte tenu de vos connaissances actuelles, vous pouvez modifier ce programme pour qu'il refuse d'enregistrer un nombre s'il n'est pas compris entre 1 et 100. Vous pourriez aussi écrire une fonction qui permet de choisir la taille de la liste.

En résumé

Vous savez maintenant créer une liste.

Vous savez utiliser une boucle for pour parcourir cette liste.

Nous avons appris à utiliser la fonction range(), qui retourne un générateur que l'on peut parcourir avec une boucle for. Nous avons vu également la fonction len() qui donne le nombre d'éléments d'une liste.

Nous avons manipulé encore une fois les fonctions qui permettent d'organiser le code et le rendre fonctionnel.

Nous avons vu les boucles while.

Vous savez presque tout ce qu'il faut pour programmer en python. Il reste une dernière chose à voir, la notion d'objet.

results matching ""

    No results matching ""