|
Publicité ' | ||||||||||||||||||||||||
|
|
#1 |
![]() ![]() Inscription : juin 2006 Messages : 6 935 ![]() |
Bonjour,
Pour ce premier défi, l'équipe de developpez.com vous propose un challenge assez simple pour débuter. Le problème étant le suivant : Challenge : Soit un entier n en entrée, Déterminer tous les ensembles de nombre entier positif dont la somme vaut n. Par exemple pour 5, tous les ensembles de nombre dont la somme vaut 5 sont :
A noter que l'on souhaite des ensembles, il n'y a donc pas de doublon (on a une seule fois (3,2), et pas : (3,2) et (2,3) par exemple). Il n'y a pas de règle sur le format de sortie. Le format standard pour les langages fonctionnels pourra par exemple être une liste de liste : [[5], [4,1], [3,2], [3,1,1], [2,2,1], [2,1,1,1], [1,1,1,1,1]] Pour un langage impératif, la sortie pourra simplement être écrite sur la sortie standard. Les règles Il n'y a pas de règle particulière (évidemment, il faut que la solution proposée fonctionne). Vous pouvez proposer des solutions aussi bien dans des langages fonctionnels (caml, haskell, scheme, lisp...) qu'impératif. Le public pourra ainsi juger du code suivant divers critères :
Le public pourra également juger les différences entre une solution fonctionnelle et une solution impérative. Il lui sera ainsi plus facile de voir, pour un même problème, les différences entre divers paradigmes. Pour répondre, il vous suffit de poster à la suite. A vos claviers de votre participation.__________________________ Sujet proposé par Jedaï
__________________
Je ne répondrai à aucune question technique en privé |
|
|
00
|
|
|
#2 | ||||
|
Membre Expert
![]() Inscription : mars 2002 Messages : 962 ![]() |
Solution en F# :
Code F# :
Exemple d'appel : Code :
|
||||
|
|
00
|
|
|
#3 | ||
|
Expert Confirmé Sénior
![]() ![]() |
Effectivement, c'est l'algorithme le plus naturel à mon avis, et les "list comprehension" permettent de l'exprimer de façon compacte :
Code Haskell :
J'explique la solution : On utilise une fonction auxiliaire csums() qui prend deux paramètres n et m et renvoie la liste des ensembles de nombres inférieurs à m dont la somme est n. Cette fonction marche de la façon suivante : Si n = 0, on considère que l'ensemble vide est la seule solution (dans ce problème on n'utilise pas le 0 dans les solutions, sinon il y en aurait une infinité), d'où le : (le _ peut reconnaître n'importe quoi, on ne se soucie pas de la valeur de m dans ce cas) Sinon si n > 0 (on ne vérifie pas ici à vrai dire, mais la structure de la fonction fait que si n < 0, elle renverra [], la liste vide, ce qui est correct), alors on utilise un générateur de liste par compréhension, qui ressemble beaucoup à une définition d'ensemble en mathématiques. En gros la définition mathématiques correspondante serait : ensemble des "x suivi de xs" tels que x appartient à l'ensemble des entiers de 1 au minimum de n et m et xs appartient à l'ensemble des ensembles de nombres inférieurs à x dont la somme est (n - x). Code :
csums n m = [x:xs | x <- [1..min n m], xs <- csums (n-x) x ] En notation mathématique : ![]() Cette algorithme s'appuie donc sur une récursion dont le cas de base est n = 0, ce qui est correct puisqu'à chaque appel récursif le n est diminué mais reste supérieur ou égal à 0. De plus il génère des listes triées dans l'ordre décroissant, ce qui évite les doublons. -- Jedaï |
||
|
|
00
|
|
|
#4 | ||
![]() ![]() Inscription : septembre 2003 Messages : 4 434 ![]() |
Une solution Prolog, simple mais pas optimisée puisqu'elle balaie toutes les solutions, le nettoyage se faisant par un tri puis une élimination des doublons avec le setof.
Code :
__________________
"La haine seule fait des choix" - Koan Zen "Il ne faut pas être meilleur que les autres, il faut être meilleur que soi." Albert Jacquard "Ceux qui savent où ils ont posé leur parapluie ne sont pas alcooliques." - pgibonne. Faites du Prolog, ça vous changera les idées ! Ma page Prolog Mes codes sources commentés Mon avatar : Intérieur avec jeune femme de Vilhelm Hammershoi |
||
|
|
00
|
|
|
#5 | ||
|
Expert Confirmé Sénior
![]() ![]() |
Les plus exigeants d'entre vous auront peut-être remarqué que pour l'instant aussi bien ma solution que celle de LLB répètent inutilement certains appels à la fonction auxiliaire (csums dans mon cas, sumrec dans le cas de LLB) durant la récursion... Ces appels pourraient être éliminés en mémorisant les résultats intermédiaires.
Voici une solution utilisant cette optimisation en Haskell : Code Haskell :
a est un tableau (array), ou plutôt une matrice, dont les indices vont de (0,0) à (n,n) (le premier argument de la fonction array donne les bornes de ses indices), et dont les valeurs sont données par la liste associative en second argument. Une liste associative est une liste de paire (a,b) où a est la clé et b la valeur associée à la clé. Si (a,b) se trouve dans la liste associative, le tableau produit par array() aura la valeur b à l'indice a. Ici tout le travail est effectué dans cette liste associative : à l'indice (k,l) (pour k variant de 1 à n et l variant de 1 à k), elle associe le résultat de csums k l, sauf qu'au lieu d'appeler récursivement une fonction, elle récupère directement les résultats intermédiaires dont elle a besoin dans le tableau a... Nous voyons ici l'une des force d'Haskell puisque la définition du tableau a utilise le tableau a. Ceci n'est possible que grâce à la paresse d'Haskell, qui ne calcule une valeur que s'il en a besoin. En conséquence de quoi, les éléments de ce tableau a ne sont calculés qu'à partir du moment où on les utilises, ce qui est le cas lorsqu'on demande "a ! (n,n)" (l'équivalent de a[n][n] ou a[n,n] dans d'autres langages). L'ordre des éléments dans la liste associative n'a aucune importance (bien qu'ici ils soient "dans le bon ordre" par pure coïncidence, cela n'a aucune influence sur l'ordre d'exécution). Cette version bien que légèrement plus compliquée (mais néanmoins tout à fait abordable puisque j'ai écris cette fonction en moins d'une minute), a des performances très nettement supérieur à la précédente. Je précise que cette fonction reste complètement dans le paradigme fonctionnel, d'ailleurs le tableau a est fonctionnel puisque immuable. -- Jedaï PS : Certains d'entre vous auront peut-être remarqué que la moitié du tableau reste inoccupée et inutile. Il est facile de remédier à cela en définissant un nouveau type Pair x y où x >= y et en en faisant une instance de la type class Ix (Index), mais il s'agit là de sujets avancés d'Haskell, qui ne relèvent pas vraiment du sujet. |
||
|
|
00
|
|
|
#6 | ||
![]() ![]() ![]() Nicolas ValléeIngénieur d'études Inscription : décembre 2005 Messages : 9 963 ![]() |
Une implémentation "triviale" en OCaml... histoire de donner un exemple
![]() Code :
la complexité temporelle est exponentielle en le premier argument, et l'occupation mémoire l'est aussi (mais c'est le problème qui le veut ) => j'arrive à calculer jusqu'à 17 puis stack overflow si j'ajoutais la mémorisation des résultats précedemment calculer (on se sert en effet de toutes les valeurs de sumrec i pour 0<i<n pour calculer sumrec n), le temps de calcul serait linéaire, mais la mémoire exploserait plus tot
|
||
|
|
00
|
|
|
#7 |
|
Membre Expert
![]() Inscription : mars 2002 Messages : 962 ![]() |
Gogonite > j'ai testé ton code, mais ça marche pas vraiment (ou y a un truc que j'ai pas compris ?).
Si certains veulent tester ma version F#, il faut ajouter "#light" (pour prendre en compte l'indentation) et ajouter à la fin la ligne : Code :
Sys.argv.(1) |> int_of_string |> sums |> print_any |
|
|
00
|
|
|
#8 | |
|
Expert Confirmé Sénior
![]() ![]() |
Bon, en dehors de quelques petits problèmes dans la fonction (genre (n-i) à la place de i), quelques remarques :
Citation:
De mon côté j'ai la fonction suivante pour les n-uplets, elle monte jusqu'à 24 en 18 secondes (toujours en interprété) et avec 500 Mo de RAM au moins d'occupé... Là je n'ose pas la pousser plus loin ! (on notera bien sûr que pour 24, il y a 2^23 n-uplets solutions, l'occupation en RAM est plutôt honnête et même étrange, compte tenu que cela fait 8_388_608 liste de longueur moyenne 12.5... Autrement dit 104857600 * 8 octets (en espérant que les Int soient boxés, ce qui en mode interprété Haskell n'est pas probable)... Impossible n'est-ce pas (cela fait déjà plus de 500 Mo) ? Nous y reviendrons). Code Haskell :
nsums = [[]]:[[x:xs | x <- [1..n], xs <- nsums !! (n - x) ] | n <- [1..]] Ma fonction n'en est pas une, c'est une liste infinie (mais paresseuse, donc tout va bien ne vous inquiétez pas). Pour trouver l'ensemble des n-uplets de nombres dont la somme est 24, il suffit de regarder au 24ème indice de cette liste : ( liste !! 0 renvoie le premier élément d'une liste, tableau ! i renvoie la valeur à l'indice i d'un tableau en Haskell) Mais la mémoire devrait éclater avec toutes les listes intermédiaires, non ? En fait il y a un "truc" (dont devrait aussi bénéficier Gorgonite, c'est pourquoi je suis étonné qu'il ait un problème à 17 éléments), réfléchissez bien !! -- Jedaï |
|
|
|
00
|
|
|
#9 | ||
|
Expert Confirmé Sénior
![]() ![]() |
La fonction de Gorgonite en Haskell (ou du moins sa copie conforme, l'algorithme est strictement le même) :
Code :
Cette fonction est légèrement plus lente que la liste infinie et occupe à peu près autant de place. -- Jedaï |
||
|
|
00
|
|
|
#10 | ||
|
Expert Confirmé Sénior
![]() ![]() |
Puisque tout le monde mets une version compilable de son programme, je vais en mettre une également :
Code Haskell :
Notez bien que cette version ne pourra pas vous servir de benchmark : tout ce que vous mesurerez c'est la vitesse des IO sur votre console... Rajoutez plutôt un length après le print si vous voulez mesurer les vitesses d'exécution. read() est une fonction qui prend une chaîne en argument et essaie de la parser de façon à produire le type exigé par le contexte (ici il s'agit de Int). -- Jedaï |
||
|
|
00
|
|
|
#11 |
|
Membre Expert
![]() Inscription : avril 2007 Messages : 829 ![]() |
> Mais la mémoire devrait éclater avec toutes les listes intermédiaires, non ?
> En fait il y a un "truc" (dont devrait aussi bénéficier Gorgonite, c'est > pourquoi je suis étonné qu'il ait un problème à 17 éléments), réfléchissez > bien !! Le 'truc' me semble être le partage implicite des listes. Comme tu travailles sur une liste paresseuse, si pour deux mêmes valeurs de (n - x), la liste renverra bien le même élément (physique), il y aura donc partage des queues de liste. Cependant, je pense que contrairement à ce que tu dis, Gorgonite n'en profite pas : Code :
List.map (fun l -> (n-i)::l) (sumrec (n-i)) Pour obtenir un partage des listes, il faudrait construire la fonction "à l'envers", en passant (n-i) en argument supplémentaire à sumrec, comme on le fait pour les fonction tail-récursives, afin qu'il l'ajoute au *fond* de toutes les listes générées, où il pourrait effectivement être partagé. (En gros, ses listes formes un arbre implicite, mais dans le mauvais sens : il faut le retourner, pour que la racine coincide avec les queues des listes) |
|
|
00
|
|
|
#12 | ||
|
Membre Expert
![]() Inscription : avril 2007 Messages : 829 ![]() |
Histoire de vérifier ces histoires de partage, j'ai fait le test sur mon ordinateur.
Voici ma version, que j'espère "avec partage". Attention, j'ai aussi fait joujou avec la syntaxe, histoire de (re)voir à quel point on pouvait faire aussi rigolo que père Curry. Je suis assez satisfait. Code :
Je dois avouer que je suis un peu décu, je m'attendais à des résultats plus différents encore. Je dois avoir raté quelque chose. |
||
|
|
00
|
|
|
#13 | |||
|
Membre Expert
![]() Inscription : mars 2002 Messages : 962 ![]() |
Citation:
Et juste pour exemple, j'ai traduit le code de Jedai en F# : (fichier complet et exécutable) Code :
Utiliser l'évaluation paresseuse n'est pas dans mes habitudes, et je n'y aurais probablement pas pensé sans Jedai, mais je suis plutôt satisfait du résultat. Le code n'est finalement pas trop compliqué. (Évidemment, les solutions données pour OCaml fonctionnent aussi bien sous F#) |
|||
|
|
00
|
|
|
#14 |
|
Membre Expert
![]() Inscription : avril 2007 Messages : 829 ![]() |
Je trouve d'ailleurs qu'un tableau est plus adapté qu'une liste pour une solution de mémoization générique comme utilisé ici (évidemment, la définition récursive de la liste est très élégante, et si on a besoin de calculer sums plusieurs fois de suite la solution est bien meilleure).
En écrivant concatMap à la main (et oui, pas de déforestation chez nous :p), la consommation mémoire passe à 33 Mo, soit trois fois moins que la version intiale. (Évidemment, les solutions données pour F# fonctionnent aussi bien sous OCaml) |
|
|
00
|
|
|
#15 | |
|
Expert Confirmé Sénior
![]() ![]() |
Citation:
Malheureusement la fonction de bluestorm donne des résultats fantaisistes... pour l'instant. -- Jedaï |
|
|
|
00
|
|
|
#16 | ||
|
Expert Confirmé Sénior
![]() ![]() |
Citation:
Citation:
Par ailleurs je répète ici que pour benchmarker vos fonction, il vaut mieux leur faire afficher la longueur de la liste résultat : si vous affichez la liste elle-même, la seule chose que vous benchmarkez c'est la vitesse d'affichage dans votre console... -- Jedaï |
||
|
|
00
|
|
|
#17 | ||
|
Membre Expert
![]() Inscription : avril 2007 Messages : 829 ![]() |
Oui évidemment (enfin maintenant qu'on me le dit), ce n'est pas
sumrec ((n - i) :: fond) (n - i) mais sumrec (i :: fond) (n - i) Et en plus, elle renvoie des solutions identiques, donc elle est vraiment à jeter Cependant, j'estime bénéficier de circonstances particulièrement atténuantes : 1) la faute vient initialement de gorgonite, j'ai juste recopié son code; 2) mes tests fort sérieux ont montré que pour 0, 1 et 2, le résultat était correct. Citation:
(#light ressemble à une directive préprocesseur. Tous les coups sont permis !) Citation:
|
||
|
|
00
|
|
|
#18 | ||
|
Expert Confirmé Sénior
![]() ![]() |
Ok, pas si tu rediriges la sortie vers /dev/null.
Voici en tout cas une version correcte du code de bluestorm, bien qu'il génère encore les n-uplets et pas les ensembles : Code OCaml :
-- Jedaï |
||
|
|
00
|
|
|
#19 | ||
![]() ![]() ![]() Nicolas ValléeIngénieur d'études Inscription : décembre 2005 Messages : 9 963 ![]() |
bon, c'était la saint gorgonite cette nuit on dirait... pour infos, le coup du (n-i) au lieu de i était une erreur de recopie (mon code était un peu plus moche, car je testais des listes mutables & cie pour éviter de dupliquer les bouts de listes
)Code ocaml :
finalement, je suis revenu sur une solution proche de celle de Jedai et LLB (j'avais testé jusqu'à 2... et n'avait pas fait gaffe qu'au delà, ça renvoyait les nuplets et non les ensembles) pour infos, cela met + 19ms pour calculer le résultat pour 21 + 10s pour 54 + stack overflow pour 55 |
||
|
|
00
|
|
|
#20 | |||
|
Membre Expert
![]() Inscription : mars 2002 Messages : 962 ![]() |
Citation:
EDIT Non, décidément, je n'arrive pas à convenir ma fonction en Caml. Le problème vient de la récursion mutuelle entre le tableau et son initialisation. Même un code aussi simpliste que : Code :
|
|||
|
|
00
|
Copyright © 2000-2013 - www.developpez.com