Aller au contenu

prog bash récursif factorielle


jakol

Messages recommandés

prog bash récursif factorielle

Bonjour,

je souhaiterais avoir des suggestions de code pour un programme récursif comptant la factoriel d'une valeur donnée.

J'ai fait le prog en itératif, mais en récursif...

voici ci-joint mon prog en itératif:

#!/bin/bash

declare -i r=1

for ((i=1;i<=$1;i++))

do

((r=r*i))

done

echo "factorielle = " $r

~

Merci d'avance pour vos réponses

Lien vers le commentaire
Partager sur d’autres sites

Ton code est bon, encore que je ne vois pas à quoi sert declare (et le seul declare que connait man sur mon serveur est celui utilisé en SQL). J'imagine qu'il est équivalent à "r=1".

Pour ce qui est de mettre en place un récursion, ce n'est pas bien dur à condition d'avoir fait l'effort de comprendre le principe de la récursion. Ci-dessous un code récursif qui ne fait pas grand chose (mais que tu vas pouvoir adapter à tes besoins après coup) :

#!/bin/bash
#
# Le script s'appelle "testreq" et est dans le PATH

if [ $1 -eq 0 ]
then
#ps -edf | grep testreq
echo "FIN"
else
echo "CONT $1"
testreq $(( $1 - 1 ))
fi

A noter que ce code a plusieurs défauts :

  • On ne vérifie pas avant traitement que la valeur passée en paramètre est bien un nombre.
  • En faisant en sorte que le script s'appelle lui-même, on entraine la création d'un nombre de processus bash équivalent au nombre d'itérations du script, ce qui n'est pas souhaitable. Cela se vérifie bien en décommentant la ligne précédent l'affichage de "FIN". La solution est de déclarer une fonction récursive au sein du script, qui n'est plus appelé qu'une seule fois.

A noter enfin qu'il n'y a pas un algorithme de récursion possible mais deux (en espérant que je ne me trompe pas dans la terminologie, mes cours étant loins...) :

  • Récursion "classique" : on lance un calcul dans lequel il manque une valeur intermédiaire déterminée par récursion ; il faut donc attendre que les calculs "plus simples" soient effectués avant que le calcul qui nous intéresse puisse se faire, et éventuellement retourne sa valeur au calcul en attente un niveau au-dessus.
  • Récursion "terminale" : plutôt que d'attendre après appel qu'une valeur nous soit retournée, on effectue notre part de calcul et envoyons la valeur calculée intermédiaire au script appelé (en plus de la valeur qui contrôle l'arrêt de l'itération), qui l'utilisera pour effectuer sa part de calcul et l'enverra au script à se suite, etc. jusqu'à la fin de l'itération, où on disposera déjà de la valeur finale, les calculs nécessaires ayant été effectués avant coup plutôt qu'après coup dans la solution "classique".

Un bon exercice étant, bien évidemment, d'implémenter les deux solutions. :francais:

Lien vers le commentaire
Partager sur d’autres sites

note que pour la factorielle, c'est un peu tricky car il va falloir multiplier n avec factorielle(n-1). Donc finalement, c'est le résultat de ce produit que ton scripte de factorielle doit afficher. Du coup, le résultat du niveau n-1 est pipé de la sortie standard vers une variable du niveau n. :francais:

Lien vers le commentaire
Partager sur d’autres sites

le seul declare que connait man sur mon serveur est celui utilisé en SQL
Essaye man bash, c'est une builtin
Le script s'appelle "testreq" et est dans le PATH
Pour faire quelque chose comme ça, il est bien plus simple de faire la récursion sur une fonction et non pas sur un fichier.
A noter enfin qu'il n'y a pas un algorithme de récursion possible mais deux (en espérant que je ne me trompe pas dans la terminologie, mes cours étant loins...) :
  • Récursion "classique" : on lance un calcul dans lequel il manque une valeur intermédiaire déterminée par récursion ; il faut donc attendre que les calculs "plus simples" soient effectués avant que le calcul qui nous intéresse puisse se faire, et éventuellement retourne sa valeur au calcul en attente un niveau au-dessus.
  • Récursion "terminale" : plutôt que d'attendre après appel qu'une valeur nous soit retournée, on effectue notre part de calcul et envoyons la valeur calculée intermédiaire au script appelé (en plus de la valeur qui contrôle l'arrêt de l'itération), qui l'utilisera pour effectuer sa part de calcul et l'enverra au script à se suite, etc. jusqu'à la fin de l'itération, où on disposera déjà de la valeur finale, les calculs nécessaires ayant été effectués avant coup plutôt qu'après coup dans la solution "classique".

Ce n'est pas exactement ça (ou alors je n'ai pas très bien compris ce que tu as voulu dire.

Il y a trois possibilités pour faire un calcul "en boucle".

  • L'itératif, c'est ce que tout le monde connait (avec un for ou un while).
  • Le récursif : C'est le fait pour une fonction de s'appeler elle même et de prendre en compte la valeur de retour afin de retourner sa propre valeur.
  • Le récursif terminal : Lorsque l'on a des ressources limités (mémoire, disque, ...) et que l'on veut quand même faire du récursif (plus gourmand que l'itératif, car plus d'appels), alors on peut utiliser une récursion bâtarde qui consiste à ce qu'une fonction s'appelle elle même et se termine aussitôt (et qui ressemble du coup beaucoup à de l'itératif niveau implémentation). Du coup pas de pile contrairement au récursif classique.

Lien vers le commentaire
Partager sur d’autres sites

Y a juste un truc qui me chagrinne dans votre érécursion terminale" : la fonction appelante est forcément en attente du résultat (à moins de jouer du jump en assembleur), donc on empile quand même un paquet de merde. On empile même un plus gros paquet que dans la version récursive, car en plus de la variable récursive, il faut faire passer les résultats intermédiaire par la pile.

Perso, je trouve ça inefficace au possible, mais peut-être me trompe-je.

Lien vers le commentaire
Partager sur d’autres sites

Le script s'appelle "testreq" et est dans le PATH
Pour faire quelque chose comme ça, il est bien plus simple de faire la récursion sur une fonction et non pas sur un fichier.

D'où mes commentaires en-dessous de mon code. Compte-tenu du problème qui nous a été présenté, j'ai supposé que notre ami jakol n'avait pas une grande expérience en bash, et je suis resté dans l'idée de faire dans le plus simple possible (quitte à faire dans l'idiot, l'objectif étant d'être didactique avant d'être complet -- même si vu la problématique, je te l'accorde, la mise en place d'une fonction n'aurait pas rendu le code moins clair :-D ).

Sinon pour la récursion terminale, quand bien même j'ai tenté de présenter la chose j'avais les mêmes doutes que lorinc. Jusqu'à ce que Wikipedia me rappelle dans quels cas la mise en place de tels algorithmes étaient utiles (j'ai adoré faire du Scheme en école d'ingé, mais je n'y ai pas retouché depuis :chinois: ).

Lien vers le commentaire
Partager sur d’autres sites

  • 1 mois après...
Y a juste un truc qui me chagrinne dans votre érécursion terminale" : la fonction appelante est forcément en attente du résultat (à moins de jouer du jump en assembleur), donc on empile quand même un paquet de merde. On empile même un plus gros paquet que dans la version récursive, car en plus de la variable récursive, il faut faire passer les résultats intermédiaire par la pile.

Perso, je trouve ça inefficace au possible, mais peut-être me trompe-je.

La récursion terminale, ça n'est utile que dans les langages (majoritairement fonctionnels, je pense à OCaml, Scheme, diallectes de Lisp, etc.) qui l'optimisent en évitant "d'empiler le paquet de merde", par modification flot d'exécution... Ça permet d'« émuler » des structures itératives efficacement en fonctionnel pur... Mais effectivement en bash, mis à part faire péter la pile d'appels (un petit peu) plus vite je vois pas bien l'intérêt :)

A+,

Lien vers le commentaire
Partager sur d’autres sites

ah si ah si, ça empile un paquet de merde :D voilà comment ça se désassemble :

08048344 <test>:
8048344:	   55					  push   %ebp
8048345:	   89 e5				   mov	%esp,%ebp
8048347:	   83 ec 08				sub	$0x8,%esp
804834a:	   83 7d 08 05			 cmpl   $0x5,0x8(%ebp)
804834e:	   7e 13				   jle	8048363 <test+0x1f>
8048350:	   8b 45 08				mov	0x8(%ebp),%eax
8048353:	   83 e8 01				sub	$0x1,%eax
8048356:	   89 04 24				mov	%eax,(%esp)
8048359:	   e8 e6 ff ff ff		  call   8048344 <test>
804835e:	   89 45 fc				mov	%eax,0xfffffffc(%ebp)
8048361:	   eb 02				   jmp	8048365 <test+0x21>
8048363:	   eb 03				   jmp	8048368 <test+0x24>
8048365:	   8b 45 fc				mov	0xfffffffc(%ebp),%eax
8048368:	   c9					  leave  
8048369:	   c3					  ret

C'est à dire un bon vieux push des familles au début, on compare à 5, si le test est faux, on retourne, si le test est vrai, on fait une soustraction et un bon gros call.

Je te parie que je peux faire peter une pile en ajoutant un truc assez gros en second argument (style une grosse structure). :zarb:

exemple

struct my_str_t {
	long double tab[10000];
};
typedef struct my_str_t my_str;


int test(int truc, my_str s, int i)
{
	printf("%d\n", i);
	if (truc>5)
			return test (truc-1, s, ++i);
	return truc;
}


int main(void)
{
	my_str s;

	test(1000, s, 1);

	return 0;
}

Si tu arrives à faire les 995 itérations demandées, bravo ;)

Lien vers le commentaire
Partager sur d’autres sites

gcc est malin, je viens d'essayer avec un -O3 et ça se désassemble comme ça :

08048350 <test>:
8048350:	   55					  push   %ebp
8048351:	   89 e5				   mov	%esp,%ebp
8048353:	   8b 55 08				mov	0x8(%ebp),%edx
8048356:	   83 fa 05				cmp	$0x5,%edx
8048359:	   7e 60				   jle	80483bb <test+0x6b>
804835b:	   83 fa 06				cmp	$0x6,%edx
804835e:	   74 59				   je	 80483b9 <test+0x69>
8048360:	   83 fa 07				cmp	$0x7,%edx
8048363:	   74 54				   je	 80483b9 <test+0x69>
8048365:	   83 fa 08				cmp	$0x8,%edx
8048368:	   74 4f				   je	 80483b9 <test+0x69>
804836a:	   83 fa 09				cmp	$0x9,%edx
804836d:	   8d 76 00				lea	0x0(%esi),%esi
8048370:	   74 47				   je	 80483b9 <test+0x69>
8048372:	   8d 42 fb				lea	0xfffffffb(%edx),%eax
8048375:	   83 f8 05				cmp	$0x5,%eax
8048378:	   74 3f				   je	 80483b9 <test+0x69>
804837a:	   83 f8 06				cmp	$0x6,%eax
804837d:	   8d 76 00				lea	0x0(%esi),%esi
8048380:	   74 37				   je	 80483b9 <test+0x69>
8048382:	   8d 42 f7				lea	0xfffffff7(%edx),%eax
8048385:	   eb 2d				   jmp	80483b4 <test+0x64>
8048387:	   83 f8 04				cmp	$0x4,%eax
804838a:	   74 2d				   je	 80483b9 <test+0x69>
804838c:	   83 f8 05				cmp	$0x5,%eax
804838f:	   90					  nop	
8048390:	   7e 29				   jle	80483bb <test+0x6b>
8048392:	   83 f8 06				cmp	$0x6,%eax
8048395:	   74 22				   je	 80483b9 <test+0x69>
8048397:	   83 f8 07				cmp	$0x7,%eax
804839a:	   74 1d				   je	 80483b9 <test+0x69>
804839c:	   83 f8 08				cmp	$0x8,%eax
804839f:	   90					  nop	
80483a0:	   74 17				   je	 80483b9 <test+0x69>
80483a2:	   83 f8 09				cmp	$0x9,%eax
80483a5:	   74 12				   je	 80483b9 <test+0x69>
80483a7:	   83 f8 0a				cmp	$0xa,%eax
80483aa:	   74 0d				   je	 80483b9 <test+0x69>
80483ac:	   83 e8 09				sub	$0x9,%eax
80483af:	   83 f8 02				cmp	$0x2,%eax
80483b2:	   74 05				   je	 80483b9 <test+0x69>
80483b4:	   83 f8 03				cmp	$0x3,%eax
80483b7:	   75 ce				   jne	8048387 <test+0x37>
80483b9:	   5d					  pop	%ebp
80483ba:	   c3					  ret	
80483bb:	   5d					  pop	%ebp
80483bc:	   8d 74 26 00			 lea	0x0(%esi),%esi
80483c0:	   c3					  ret	
80483c1:	   eb 0d				   jmp	80483d0 <main>

et hop, disparu le call à test, remplacé par des jumps, dont un vers main pour la sortie finale. :D

Lien vers le commentaire
Partager sur d’autres sites

Archivé

Ce sujet est désormais archivé et ne peut plus recevoir de nouvelles réponses.

×
×
  • Créer...