Programmer un robot (4)

Série d'exercices

Cet exercice fait partie d'une série :

Il est fortement conseillé d'avoir traité les exercices précédents avant d'entreprendre celui-ci.

Rappels sur le fonctionnement du Robot

On considÚre dans cet exercice un robot se déplaçant sur une grille de dimensions finies. Initialement, il se trouve sur la case en haut à gauche de la grille et est dirigé vers la droite.

Ce robot est représenté en Python par un objet de la classe Robot. L'interface de la classe Robot est la suivante :

  • robot = Robot(4, 5) : instancie un objet de type Robot Ă©voluant dans une grille de 4 cases de haut et 5 de large. Cet objet est affectĂ© Ă  la variable robot ;

  • robot.avance() : fait avancer le robot d'une case dans la direction actuelle. Un dĂ©placement qui ferait sortir le robot de la grille est ignorĂ© ;

  • robot.droite() : fait tourner le robot d'un quart de tour vers la gauche ;
  • robot.gauche() : fait tourner le robot d'un quart de tour vers la gauche ;
  • robot.dessine_parcours() : affiche la grille, les cases dĂ©jĂ  parcourues et la position actuelle du robot dans la console.

Un objet de type Robot contient aussi un attribut grille. Il s'agit d'une liste de listes gardant la trace des cases visitées (marquées par "*") ou non (laissées vides " ").

Exemple d'utilisation d'un Robot
>>> robot = Robot(4, 3)       # la grille fait 4 case de haut sur 3 de large
>>> robot.grille              # le robot est en haut Ă  gauche
[['*', ' ', ' '], [' ', ' ', ' '], [' ', ' ', ' '], [' ', ' ', ' ']]
>>> robot.dessine_parcours()  # le robot pointe vers la droite
┌───┐
│>  │
│   │
│   │
│   │
└───┘
>>> robot.avance()
>>> robot.avance()
>>> robot.droite()
>>> robot.avance()
>>> robot.avance()
>>> robot.avance()
>>> robot.dessine_parcours()
┌───┐
│***│
│  *│
│  *│
│  v│
└───┘
>>> robot.gauche()
>>> robot.avance()
>>> robot.dessine_parcours()
┌───┐
│***│
│  *│
│  *│
│  >│
└───┘
>>> robot.grille
[['*', '*', '*'], [' ', ' ', '*'], [' ', ' ', '*'], [' ', ' ', '*']]
La classe Robot
MOUVEMENTS = ((0, 1), (1, 0), (0, -1), (-1, 0))


class Robot:
    def __init__(self, hauteur, largeur):
        self.hauteur = hauteur
        self.largeur = largeur
        self.grille = [[" " for _ in range(largeur)] for _ in range(hauteur)]
        self.i = 0
        self.j = 0
        self.grille[self.i][self.j] = "*"
        self.direction = 0

    def avance(self):
        """Fait avancer le robot d'une case (seulement si possible)"""
        di, dj = MOUVEMENTS[self.direction]
        if 0 <= self.i + di < self.hauteur and 0 <= self.j + dj < self.largeur:
            self.i += di
            self.j += dj
            self.grille[self.i][self.j] = "*"

    def droite(self):
        """Fait tourner le robot d'un quart de tour vers la droite"""
        self.direction = (self.direction + 1) % 4

    def gauche(self):
        """Fait tourner le robot d'un quart de tour vers la gauche"""
        self.direction = (self.direction - 1) % 4

    def dessine_parcours(self):
        """Affiche les cases parcourues et la position actuelle du robot"""
        affichage = [
            ["" for _ in range(self.largeur + 2)] for _ in range(self.hauteur + 2)
        ]
        for j in range(1, self.largeur + 1):
            affichage[0][j] = "─"
            affichage[-1][j] = "─"
        for i in range(1, self.hauteur + 1):
            affichage[i][0] = "│"
            affichage[i][-1] = "│"
        affichage[0][0] = "┌"
        affichage[-1][0] = "└"
        affichage[0][-1] = "┐"
        affichage[-1][-1] = "┘"
        for i in range(self.hauteur):
            for j in range(self.largeur):
                affichage[1 + i][1 + j] = self.grille[i][j]
        affichage[self.i + 1][self.j + 1] = [">", "v", "<", "^"][self.direction]
        print("\n".join("".join(ligne) for ligne in affichage))

La classe Robot est déjà chargée dans l'éditeur, vous pouvez l'utiliser sans l'importer.

On programme ce robot en lui faisant effectuer différentes actions :

  • le code "A" fait avancer le robot ;
  • le code "D" le fait tourner vers la droite ;
  • le code "G" le fait tourner vers la gauche.

Il est aussi possible d'utiliser des instructions du type [action, n] dans lesquelles :

  • action est une des trois actions dĂ©crites ci-dessus ;
  • n est un nombre entier positif ou nul.

On autorise aussi les répétitions de motifs en écrivant des instructions au format ["(", <instructions>, ")", n] dans lesquelles :

  • <instructions> est une suite d'instructions valides ;
  • n est un nombre entier positif ou nul.

Il est possible d'imbriquer des motifs répétés comme dans ["(", "(", "A", 2, "D", ")", 4, "A", 8, "D", ")", 4].

Malgré la possibilité de répéter des motifs introduite dans cet exercice, certaines suites d'instructions restent longues. Par exemple, la liste suivante permet de dessiner deux rectangles identiques séparés de 15 pas : ["(", "A", 5, "D", "A", 3, "D", ")", 4, "G", "A", 15, "(", "A", 5, "D", "A", 3, "D", ")", 4].

On introduit donc la possibilité de déclarer des motifs et de les utiliser dans les instructions.

La déclaration d'un motif suit la syntaxe ["debut_motif", <nom_motif>, <instructions>, "fin_motif"] :

  • <nom_motif> est une chaĂźne de caractĂšres quelconque, diffĂ©rente de "A", "D", "G", "(", ")", "debut_motif" et "fin_motif" ;
  • <instructions> est une suite d'instructions valides.

L'utilisation d'un motif se fait en appelant simplement son nom.

Ainsi, l'exemple précédent peut s'écrire : ["debut_motif", "rect", "(", "A", 5, "D", "A", 3, "D", ")", 2, "fin_motif", "rect", "G", "A", 15, "rect"]

On précise les points suivants :

  • la dĂ©claration d'un motif peut avoir lieu avant ou aprĂšs sa premiĂšre utilisation ;

  • les motifs peuvent contenir des instructions ou des suites d'instructions rĂ©pĂ©tĂ©es ;

  • les motifs eux-mĂȘmes peuvent ĂȘtre rĂ©pĂ©tĂ©s (par exemple ["rect", 4]) ;

  • si un motif est dĂ©clarĂ© Ă  plusieurs reprises, la dĂ©claration situĂ©e le plus loin dans la liste d'instructions prĂ©vaut.

La gestion des motifs se fait à l'aide d'un dictionnaire memoire qui associé à un nom de motif la série d'instructions correspondante.

L'interprétation des motifs peut se faire en utilisant une fonction deroule analogue à celle rencontrée dans l'exercice 3 à ceci prÚs qu'elle prend désormais un paramÚtre supplémentaire (la mémoire) et renvoie celle-ci en plus de la série d'instructions et de l'indice de la prochaine instruction à lire. On doit simplement faire en sorte, lorsque l'on rencontre un motif, de ne pas l'ajouter à la suite d'instructions déroulées mais plutÎt à la mémoire. La fonction deroule permet donc de :

  • dĂ©rouler les motifs rĂ©pĂ©tĂ©s entre parenthĂšses,

  • nettoyer la sĂ©rie d'instructions des dĂ©clarations de motifs qui sont seulement ajoutĂ©s Ă  la mĂ©moire.

Une fois la fonction deroule effectuée, on peut exécuter les instructions « déroulées » à l'aide de la fonction execute_brut qui prend en paramÚtres le robot, la liste d'instructions « déroulée » ainsi que la mémoire.

Lors de l'exécution de ces instructions élémentaires, si l'on rencontre un appel à un motif, il est possible de l'exécuter en faisant execute_brut(robot, memoire[<nom_motif>], memoire).

Écrire la fonction execute (et les fonctions deroule et execute_brut si vous le souhaitez) qui prend en paramùtres un objet de type Robot et une liste d'instructions et fait effectuer chacune de ces instructions par le robot.

On garantit que :

  • toutes les instructions sont valides ("A", "D", "G", "(", ")" ou un entier positif ou nul),

  • chaque entier suit une action, un motif valide ou une parenthĂšse fermante,

  • la suite d'instructions est correctement parenthĂ©sĂ©e,
  • les dĂ©clarations de motifs sont correctement formĂ©es,
  • les motifs utilisĂ©s ont tous Ă©tĂ© dĂ©clarĂ©s dans la suite d'instructions,
  • les motifs n'appellent pas d'autres motifs1.
Exemple
>>> robot = Robot(4, 4)
>>> instructions = ["debut_motif", "carré", "(", "A", 3, "D", ")", 4, "fin_motif", "carré"]
>>> execute(robot, instructions)
>>> robot.dessine_parcours()
┌────┐
│>***│
│*  *│
│*  *│
│****│
└────┘

###(Dés-)Active le code aprÚs la ligne # Tests (insensible à la casse)
(Ctrl+I)
Entrer ou sortir du mode "deux colonnes"
(Alt+: ; Ctrl pour inverser les colonnes)
Entrer ou sortir du mode "plein écran"
(Esc)
Tronquer ou non le feedback dans les terminaux (sortie standard & stacktrace / relancer le code pour appliquer)
Si activĂ©, le texte copiĂ© dans le terminal est joint sur une seule ligne avant d'ĂȘtre copiĂ© dans le presse-papier
Évaluations restantes : 10/10

.128013Gu;5D}0_[-q,49:dfmgA*r8kb(i]{nl.x1P2a So3)yv=wpsh6c+te7/é050q0$0#0L0B0F0W0M0Z0F0L0W0W0T010#0B0V010406050W0c0s0s0L0w0R040N0O0F0c0}0O0E050(1416181a120V04051q1j1t0(1q120q0B0S0=0@0_0{0X0B0t0X0F1H0X0#10050-0z0F0$1C0^0`011G1I1K1I0#1Q1S1O0#0w1r0#0X0=1d0W0V0L0E0{0K011U1E010r0/0$0E0L0s0$1O1;1?1{1W1~1S2123100a0M0J0w0O0V0O0W0B1g0E0M0+1/0w0w0$0Z2o1j260E1r0(1-2B1*1,1+1P0q280{1K0E202l1O1z1B0?1V2L0B2N0E0O2R1O0V2u1r2z2B2)131=2p2T1|2Y0w170F100M0I2y2-112,272/1W2;2?2^0K2{1?2}2z2K01320L2@040M0P362A1239300{3c3e0M0n3i382-3a3o2^0e3s3k3u3m3b0O2=3d2^0Y3z2~2.1D313E333f0%3J3l3M3n3O3G3f0x3S3B3U3D3F3p0o3!2 3$3w040I0h3+3L2U3%3P0I2`1k2|3A3,3@3.0I353|373~3?2:3W3e0I3h443j3K3v49100I3r4d3t3 483(4i3y4l464g4p3/3I4s4f3C413R4y3T404h3/3Z4D3#4F4v0I3*4J4n3N4v0K3;4P474R3P0K3{2)4t4A4G0K434#4z3-4(4c4+4E4o4Y4k4:4K4=3X0K4r4^4Q3V4S4x4~4W504Y4C534u4Y4I584%4S4O5c4-4v0P4U5g4L3P0P4!3}4,5m3X0P4*5q4;4X5t4/5w4_5y3e0P4@5B4 3^5t4}5H545J5E525M595t575R5d5n5b5V5h5n5f5Z5s3e0n5k5%4`5)5p455r5-100n5v5:5x553X0n5A5_5C5{5)5G5 5I3.0n5L645N665Q695S5)5U6d5W5|5Y371u2%1j2R2E0q1,2J3C0Z2Z241r6p1s6n2+4l056v0+2(60010y100+0r3s5;1W0U2^6O5`3b0r6L0$2i0c1S6T6I0 040A6$65102W0W1*0c2w0B1h0W6+5N6(0m3s0M6P3n6-6_3a6{6}6 3b10230s0O0B2u723C6(0Q0p3z0M7k6~6U0E102u140F0-0#756U0O100T7v6%100j0C7j7l766K040U1G6#4l7m6I7o040B7A5I7x04027s0d7T6a0z102b7e3$6(6*6D7n6-0E6/0w6;2o6^7-7B047h7F7l7G7.7R7:6:7!3a7V7z7N767Q6.6:6=6@7)3@6(0j8f2:717_5I6(7E4s7~7~7H6-6N88808b7=843C86872)7O5I0W1_7W0l0c0O0#0d0A028K8M7Z8m6`107i8q8r8Z89100W0O0c0W0i7q6!7t8j1W748w7P8l2+6U8;8E8#04797b7d8=7U7y8A3-6X6Z7M8^7`7,996,817;7?6?7:8/0{8`2|8F6a8@9m767V0!943@0s0B4i9j019l379n3v780$7a7c0$9z7g7}8Z7 8?041i915N869u8k9e8c7@9K7C9z8a9!048p4#9N8r8|8,7s0L7u9S85100!8D9q808%8)8+0$7r8.9?8B100v9V31109R9+9,9O9d7Sa33$9s9`9C769w9y8Y8!6U7I0$1K8v8{8x828zag3@8Ca70{8H108Q8L8N0+0z1f0i7a0}0raG8S9(8Xabac9D4Aa90O0saC019Uaz9W8y9g8e8U739#a-aX7Ra#9sa#am3/9(9*3}aV7k8|9}8*9/a29c8V046|a(a8a=a:7*10b8av9P8~9Ia?93b970040+979Jbc8g109b2|8|a*8d9ibs1|9B2AaW95bbbg92049tbm01a_5^2A76bD3f8|bi90b5a.7{9M9,bU9G8 brbX7fa/b*bG2Ya!bB8:10a|ak7wblbI9o04b19 a19;b!ad5Narata#8aax0cbk040TajbE76aE8JaH0d0QaQ8Na?6R043Ec77/9fcbcda#chcm0d1~0EaM8M0BaP8Rcnb;9k8Wc2ap6I7I2u0#0c0waa9{9Pb3c1cJ9AbecsbHcVbJbLb{3aa_5/bQ8_c#bM7QbVb)a}9N8t04as0Wc^6lc:04aTc_aV9.a08-cYb-aA100G9$100L0V0V200q9(bvd09Pa*9(0QcM9-8xcb9_a^9xa`aobF3@cP0,cScUb^cWd79:9=dabCc;c+a;afdMb=b7c$c@3z064$3$7I6M9zcp6~cZ0E6Wc|0H2v1f0$0i0z7=dLbwd1dmc/cW0O0z8M9(bfc(b|by9ZcZbSdB9WdWe7107|dAbxcvdx105+458saq107K1 c$dRe39@7W7Yc77$047(ec6)de9X7=bz7^dScK7{d3elc`6U0Z0I10030M1S2wcS1T2r0F02030P0o0de59hdsbxc9ehc=ct9Y9heHd_7`8id*9pdn8nb?e+en7RauetdQcb7XcIdP3-eyeAeIc!eCe`eEa+bAfc9LbM7V0keidzf8aAcp1?dkc=ey0B2m821?0Zc d|e}fefcc89feG9(e_fGe{cfb_bKfoc.6HfE0Ce2dH65fw0Ed^e|b6eeaUac8|dGfO6Ia%fqa)c9fJeBfLe@aedvfRa{dsemdodvcebT6UbOe cO10c}fCfTb6eL3ja~e9baf,g3f.b`f33$c-f~c39Ec%fX9T9^g2gg0{go4s06b#eOeQ04eS0$0H0)0Z1f9h2q1TeZe#e%e)1hg65I7I0rcre/040ico7/ex7p1?0tgabRbueDgig-eKgpa f0cFere-fl7ygwcg8Icz0fczaSg?dtd}d f#fDgu04ddff0q2i2ng,d`drdAgCg7c|c6gZdpg|ccg~6UcycH0d0bh3eBgd11gff 9d2ih89z7VhdfM040t0L6;0Xhi9ahkf)hmgVg8hpf:bahrh!0{aBbMhwcj0uhAfjcLhla~9.d~e0cZhKeD0L0SfAhSfE0AhUd4f*f0g9h4h;d580gHd/0#d;d?1fdleDhHh^h/dUgZebilf^f$grh$f_b6fVdVb%bjeBi245dYc{d$cZd(9$d,ibgKidihffijh9gbbYfWf-aef=e6ilf(i3e,fIiXiuiSc$g#eBiTgj9diogmdbccc$bp8(98i(b+fFi{bGgSfii~btimh%bNdyekhai)bM6(0D0giBgegqa;iKd:d=d@iNhMiPe1g`i$e=jqiniybWj2dNbZ4y0(6F0$2B2$jE6o1A6q2E2H2C0L1RjH0(6p12jR0,0.0:04.

###(Dés-)Active le code aprÚs la ligne # Tests (insensible à la casse)
(Ctrl+I)
Entrer ou sortir du mode "deux colonnes"
(Alt+: ; Ctrl pour inverser les colonnes)
Entrer ou sortir du mode "plein écran"
(Esc)
Tronquer ou non le feedback dans les terminaux (sortie standard & stacktrace / relancer le code pour appliquer)
Si activĂ©, le texte copiĂ© dans le terminal est joint sur une seule ligne avant d'ĂȘtre copiĂ© dans le presse-papier
Évaluations restantes : 10/10

.128013Gu;5D}0_[-q,49:dfmgA*r8kb(i]{nl.x1P2a So3)yv=wpsh6c+te7/é050q0$0#0L0B0F0W0M0Z0F0L0W0W0T010#0B0V010406050W0c0s0s0L0w0R040N0O0F0c0}0O0E050(1416181a120V04051q1j1t0(1q120q0B0S0=0@0_0{0X0B0t0X0F1H0X0#10050-0z0F0$1C0^0`011G1I1K1I0#1Q1S1O0#0w1r0#0X0=1d0W0V0L0E0{0K011U1E010r0/0$0E0L0s0$1O1;1?1{1W1~1S2123100a0M0J0w0O0V0O0W0B1g0E0M0+1/0w0w0$0Z2o1j260E1r0(1-2B1*1,1+1P0q280{1K0E202l1O1z1B0?1V2L0B2N0E0O2R1O0V2u1r2z2B2)131=2p2T1|2Y0w170F100M0I2y2-112,272/1W2;2?2^0K2{1?2}2z2K01320L2@040M0P362A1239300{3c3e0M0n3i382-3a3o2^0e3s3k3u3m3b0O2=3d2^0Y3z2~2.1D313E333f0%3J3l3M3n3O3G3f0x3S3B3U3D3F3p0o3!2 3$3w040I0h3+3L2U3%3P0I2`1k2|3A3,3@3.0I353|373~3?2:3W3e0I3h443j3K3v49100I3r4d3t3 483(4i3y4l464g4p3/3I4s4f3C413R4y3T404h3/3Z4D3#4F4v0I3*4J4n3N4v0K3;4P474R3P0K3{2)4t4A4G0K434#4z3-4(4c4+4E4o4Y4k4:4K4=3X0K4r4^4Q3V4S4x4~4W504Y4C534u4Y4I584%4S4O5c4-4v0P4U5g4L3P0P4!3}4,5m3X0P4*5q4;4X5t4/5w4_5y3e0P4@5B4 3^5t4}5H545J5E525M595t575R5d5n5b5V5h5n5f5Z5s3e0n5k5%4`5)5p455r5-100n5v5:5x553X0n5A5_5C5{5)5G5 5I3.0n5L645N665Q695S5)5U6d5W5|5Y371u2%1j2R2E0q1,2J3C0Z2Z241r6p1s6n2+4l056v0+2(60010y100+0r3s5;1W0U2^6O5`3b0r6L0$2i0c1S6T6I0 040A6$65102W0W1*0c2w0B1h0W6+5N6(0m3s0M6P3n6-6_3a6{6}6 3b10230s0O0B2u723C6(0Q0p3z0M7k6~6U0E102u140F0-0#756U0O100T7v6%100j0C7j7l766K040U1G6#4l7m6I7o040B7A5I7x04027s0d7T6a0z102b7e3$6(6*6D7n6-0E6/0w6;2o6^7-7B047h7F7l7G7.7R7:6:7!3a7V7z7N767Q6.6:6=6@7)3@6(0j8f2:717_5I6(7E4s7~7~7H6-6N88808b7=843C86872)7O5I0W1_7W0l0c0O0#0d0A028K8M7Z8m6`107i8q8r8Z89100W0O0c0W0i7q6!7t8j1W748w7P8l2+6U8;8E8#04797b7d8=7U7y8A3-6X6Z7M8^7`7,996,817;7?6?7:8/0{8`2|8F6a8@9m767V0!943@0s0B4i9j019l379n3v780$7a7c0$9z7g7}8Z7 8?041i915N869u8k9e8c7@9K7C9z8a9!048p4#9N8r8|8,7s0L7u9S85100!8D9q808%8)8+0$7r8.9?8B100v9V31109R9+9,9O9d7Sa33$9s9`9C769w9y8Y8!6U7I0$1K8v8{8x828zag3@8Ca70{8H108Q8L8N0+0z1f0i7a0}0raG8S9(8Xabac9D4Aa90O0saC019Uaz9W8y9g8e8U739#a-aX7Ra#9sa#am3/9(9*3}aV7k8|9}8*9/a29c8V046|a(a8a=a:7*10b8av9P8~9Ia?93b970040+979Jbc8g109b2|8|a*8d9ibs1|9B2AaW95bbbg92049tbm01a_5^2A76bD3f8|bi90b5a.7{9M9,bU9G8 brbX7fa/b*bG2Ya!bB8:10a|ak7wblbI9o04b19 a19;b!ad5Narata#8aax0cbk040TajbE76aE8JaH0d0QaQ8Na?6R043Ec77/9fcbcda#chcm0d1~0EaM8M0BaP8Rcnb;9k8Wc2ap6I7I2u0#0c0waa9{9Pb3c1cJ9AbecsbHcVbJbLb{3aa_5/bQ8_c#bM7QbVb)a}9N8t04as0Wc^6lc:04aTc_aV9.a08-cYb-aA100G9$100L0V0V200q9(bvd09Pa*9(0QcM9-8xcb9_a^9xa`aobF3@cP0,cScUb^cWd79:9=dabCc;c+a;afdMb=b7c$c@3z064$3$7I6M9zcp6~cZ0E6Wc|0H2v1f0$0i0z7=dLbwd1dmc/cW0O0z8M9(bfc(b|by9ZcZbSdB9WdWe7107|dAbxcvdx105+458saq107K1 c$dRe39@7W7Yc77$047(ec6)de9X7=bz7^dScK7{d3elc`6U0Z0I10030M1S2wcS1T2r0F02030P0o0de59hdsbxc9ehc=ct9Y9heHd_7`8id*9pdn8nb?e+en7RauetdQcb7XcIdP3-eyeAeIc!eCe`eEa+bAfc9LbM7V0keidzf8aAcp1?dkc=ey0B2m821?0Zc d|e}fefcc89feG9(e_fGe{cfb_bKfoc.6HfE0Ce2dH65fw0Ed^e|b6eeaUac8|dGfO6Ia%fqa)c9fJeBfLe@aedvfRa{dsemdodvcebT6UbOe cO10c}fCfTb6eL3ja~e9baf,g3f.b`f33$c-f~c39Ec%fX9T9^g2gg0{go4s06b#eOeQ04eS0$0H0)0Z1f9h2q1TeZe#e%e)1hg65I7I0rcre/040ico7/ex7p1?0tgabRbueDgig-eKgpa f0cFere-fl7ygwcg8Icz0fczaSg?dtd}d f#fDgu04ddff0q2i2ng,d`drdAgCg7c|c6gZdpg|ccg~6UcycH0d0bh3eBgd11gff 9d2ih89z7VhdfM040t0L6;0Xhi9ahkf)hmgVg8hpf:bahrh!0{aBbMhwcj0uhAfjcLhla~9.d~e0cZhKeD0L0SfAhSfE0AhUd4f*f0g9h4h;d580gHd/0#d;d?1fdleDhHh^h/dUgZebilf^f$grh$f_b6fVdVb%bjeBi245dYc{d$cZd(9$d,ibgKidihffijh9gbbYfWf-aef=e6ilf(i3e,fIiXiuiSc$g#eBiTgj9diogmdbccc$bp8(98i(b+fFi{bGgSfii~btimh%bNdyekhai)bM6(0D0giBgegqa;iKd:d=d@iNhMiPe1g`i$e=jqiniybWj2dNbZ4y0(6F0$2B2$jE6o1A6q2E2H2C0L1RjH0(6p12jR0,0.0:04.

  1. l'approche utilisée ici permettrait des appels imbriqués mais il faudrait toutefois mettre en place un mécanisme limitant la profondeur d'appels récursifs !