Partage
  • Partager sur Facebook
  • Partager sur Twitter

[TP] Notions de programmation fonctionnelle

map, reduce, compose… open your mind!

Sujet résolu
    27 juin 2011 à 0:13:08

    fred1559, ta fonction another ne sert pas à grand chose : tu pourrais tout simplement écrire fonction à la place ;)
    • Partager sur Facebook
    • Partager sur Twitter
    Anonyme
      27 juin 2011 à 0:21:21

      La fonction X qui prend en paramètre la fonction Y résultant de la fonction Z qui a pris la fonction X en paramètre... Heu... non c'est pas ça, merde... Où suis-je ? Dans quelle étagère ? o_O


      def mymap(funct, iterable):
          return [ funct(x)  for x in iterable ]
      
      def mymap_gen(funct, iterable):
          for x in iterable:
              yield funct(x)
      
      def myreduce(funct, iterable, initiale=None):
          if initiale is None:
              iterable, initiale = iterable[1:], iterable[0]
          for x in iterable:
              initiale = funct(initiale, x)
          return initiale
      
      def mymap2(funct, iterable):
          def f(a, b):
              return a + [funct(b)]
          return myreduce(f, iterable, [])
      
      def compose1(funct1, funct2):
          def f(x):
              return funct2(funct1(x))
          return f
      
      def compose2(*functs):
          def f(x):
              return x
          return myreduce(compose1, functs, f)
      

      • Partager sur Facebook
      • Partager sur Twitter
        27 juin 2011 à 0:39:32

        @PsycoPy, gg

        Reste plus qu'à récrire certaines fonctions avec des lambdas et ce sera top saiks.
        • Partager sur Facebook
        • Partager sur Twitter
        Zeste de Savoir, le site qui en a dans le citron !
        Anonyme
          27 juin 2011 à 0:49:12

          Ouais mais je n'arrive pas reduce mon reduce... :-°

          Edit: :honte: oulala... Je vais me coucher... Ça ira mieux demain...
          • Partager sur Facebook
          • Partager sur Twitter
          Anonyme
            27 juin 2011 à 10:23:49

            Un autre myreduce

            def myreduce(fonction, liste):
                acc = liste[0]
                for i in range(1, len(liste)):
                    acc = fonction(acc, liste[i])
                return acc
            
            • Partager sur Facebook
            • Partager sur Twitter
              27 juin 2011 à 10:29:55

              Voilà une proposition de correction complète et commentée :


              # -*- coding: utf-8 -*-
              
              """
              Les exemples qui suivent sont des implémentations en Python de plusieurs
              routines classiques en programmation fonctionnelle.
              
              """
              
              
              def mymap_it(fn, lst):
                  """ Implémentation de 'map' de façon itérative. """
                  res = list()
                  for elt in lst:
                      res.append(fn(elt))
                  return res
                  
              # Implémentation de 'map' au moyen d'une list comprehension.
              mymap = lambda fn, lst: [fn(x) for x in lst]
              
              # Implémentation de 'map' au moyen d'une expression génératrice.
              mymap_gen = lambda fn, lst: (fn(x) for x in lst)
              
              
              def myreduce(fn, lst, acc = None):
                  """ Implémentation de 'reduce' de façon itérative. """
                  if acc is None:
                      acc, lst = lst[0], lst[1:]
                  for elt in lst:
                      acc = fn(acc, elt)
                  return acc
              
              def mymap2(fn, lst):
                  """ Formulation de 'map' en fonction de 'reduce'. """
                  acc_fn = lambda acc, elt: acc + [fn(elt)]
                  return myreduce(acc_fn, lst, [])
              
              # One-liner pour mymap2
              mymap3 = lambda fn, lst: myreduce(lambda acc, elt: acc + [fn(elt)], lst, [])
              
              
              def comp(f, g):
                  """ Composition de 2 fonctions. """
                  def c(x): 
                      return g(f(x))
                  return c
              
              
              # Version one-liner de la composition.
              comp1 = lambda f, g: lambda x: g(f(x))
              
              
              def compose_naive(*args):
                  """ Version 'naïve' de la composition de n fonctions. """
                  def c(x):
                      acc = x             # accumulateur de résultats
                      for fn in args:
                          acc = fn(acc)   # application successive des fonctions
                      return acc          
                  return c
              
              # Ça ressemble furieusement à une application de 'reduce', non ?
              
              def compose_naive_reduce(*args):
                  """ 
                  Version 'naïve' de la composition de n fonctions exprimée en fonction
                  de 'reduce'.
                  
                  """
                  applique = lambda acc, fn: fn(acc)
                  return lambda x: myreduce(applique, args, x)
              
              
              def compose(*args):
                  """ 
                  Composition d'un nombre arbitraire de fonctions exprimée en fonction de la
                  composition de 2 fonctions.
                  
                  """
                  acc = lambda t: t       # accumulateur: fonction identité
                  for fn in args:
                      acc = comp(acc, fn) # fct d'accumulation: composition
                  return acc
              
              # La même, en une seule instruction
              compose_lambda = lambda *args: myreduce(comp, args, lambda t: t)
              



              N'hésitez pas à poser des questions si quelque-chose vous parait obscur.

              @Fred: ouaip, c'est bon.
              • Partager sur Facebook
              • Partager sur Twitter
              Zeste de Savoir, le site qui en a dans le citron !
              Anonyme
                27 juin 2011 à 10:44:49

                Encore une autre ;)

                def myreduce(fonction, liste, acc=None):
                    if acc is None:
                        liste = iter(liste)
                        suivant = next(liste)
                    else:
                        suivant = acc
                    for i in liste:
                        suivant = fonction(suivant, i)
                        yield suivant
                


                Hoooo, je suis en forme là
                • Partager sur Facebook
                • Partager sur Twitter
                  27 juin 2011 à 13:07:32

                  C'est bien de t'entraîner, mais attention : au contraire de map, où tous les résultats nous intéressent, avec reduce, on s'attend à récupérer le résultat final. Ta fonction myreduce pourrait sans doute être utile dans certains cas en renvoyant toutes les valeurs intermédiaires, mais ce n'est pas un reduce au sens où on l'entend habituellement :)

                  J'en profite pour demander à NoHaR s'il a l'intention de faire joujou avec le sens droite/gauche de reduce ou si je vais en parler un peu tout seul.
                  • Partager sur Facebook
                  • Partager sur Twitter
                    27 juin 2011 à 13:24:16

                    Hmm nop, ça me semble pas pertinent ici de parler du sens dans lequel est faite la réduction (y'en a qu'un seul d'utilisé en Python, et on n'a pas de problème de lazy evaluation ici), mais si ça te tente, fais-toi plaisir. ;)
                    • Partager sur Facebook
                    • Partager sur Twitter
                    Zeste de Savoir, le site qui en a dans le citron !
                      27 juin 2011 à 14:35:41

                      Bonjour, pour la fonction mymap2, j'ai été obligé de regarder la solution (GurneyH et PsycoPy) et effectivement je ne l'aurais jamais pondue tout seul mais j'avais le raisonnement :euh: .

                      Pour la fonction compose1, j'ai mis un peu de temps à trouver comment passer l'argument x à la fonction c sinon ça roule.
                      • Partager sur Facebook
                      • Partager sur Twitter
                        27 juin 2011 à 16:26:13

                        Bonjour,

                        je me retrouve avec un truc pas jojo pour la fonction compose2, mais je ne vois pas comment faire autrement. :-°

                        def compose2(*args):
                            def inter(x):
                                acc = args[0]
                                for f in args[1:-1]:
                                    acc = f(acc(x))
                                acc = args[-1](acc)
                                return acc
                            return inter
                        


                        arrivé à la dernier fonction passée en argument, j'ai un
                        TypeError: 'int' object is not callable

                        si je ne bricole pas

                        edit:
                        Je viens de regarder la correction de nohar pour cette fonction.
                        Je vois mon erreur.
                        Je laisse cependant, pour montrer les difficultés qu'on peut rencontrer.
                        • Partager sur Facebook
                        • Partager sur Twitter
                        Zeste de Savoir, le site qui en a dans le citron !
                          27 juin 2011 à 16:35:44

                          Par contre j'ai du mal à comprendre cette fonction là:

                          def compose_naive_reduce(*args):
                              """ 
                              Version 'naïve' de la composition de n fonctions exprimée en fonction
                              de 'reduce'.
                              
                              """
                              applique = lambda acc, fn: fn(acc)
                              return lambda x: myreduce(applique, args, x)
                          


                          • Partager sur Facebook
                          • Partager sur Twitter
                            27 juin 2011 à 16:44:35

                            Bon, déjà la voilà en version "moins compacte":

                            def compose_naive_reduce(*args):
                                def applique(acc, fn):
                                   return fn(acc)
                                def c(x):
                                   return myreduce(applique, args, x)
                                return c
                            


                            Le principe, c'est de boucler sur la liste des fonctions en faisant à chaque fois acc = fn(acc), soit acc = applique(acc, fn), c'est-à-dire de stocker le résultat temporaire dans la variable acc, en appliquant les fonctions au fur et à mesure.

                            Si on regarde l'exemple qui permet de passer de la fonction somme à son implémentation via reduce, on s'aperçoit que la fonction c (dans compose_naive) suit exactement le même schéma, donc que l'on peut elle aussi l'exprimer en fonction de reduce, ce que j'ai fait.
                            • Partager sur Facebook
                            • Partager sur Twitter
                            Zeste de Savoir, le site qui en a dans le citron !
                              27 juin 2011 à 16:46:31

                              Citation : nohar

                              Hmm nop, ça me semble pas pertinent ici de parler du sens dans lequel est faite la réduction (y'en a qu'un seul d'utilisé en Python, et on n'a pas de problème de lazy evaluation ici), mais si ça te tente, fais-toi plaisir. ;)


                              C'est pas une question de lazy evaluation : si la fonction utilisée dans reduce n'est pas associative, changer de sens change le résultat. Effectivement en Python on n'en a qu'une de disponible dans la librairie standard, mais par exemple, si tu voulais faire compose en donnant les fonctions dans l'autre sens, tu pourrais pas sans retourner la liste (alors qu'un fold_right permettrait de le faire exactement comme on a fait notre compose ici).
                              • Partager sur Facebook
                              • Partager sur Twitter
                                27 juin 2011 à 17:36:24

                                Ah j'ai compris, en tout cas un grand merci j'ai appris pas mal de choses grâce à ce topic, il me reste plus qu'à bien assimiler tout çà :D .
                                • Partager sur Facebook
                                • Partager sur Twitter
                                  28 juin 2011 à 1:44:18

                                  Je propose une version récursive pour map et reduce, bien que la version itérative reste à mon avis beaucoup plus simple en Python.

                                  def mymap(f,L):
                                  	def _mymap(f,L,L2=[]):
                                  		if len(L) == 0:
                                  			return L2
                                  		return _mymap(f, L[1:], L2+[f(L[0])])
                                  	return _mymap(f, L)
                                  
                                  def myreduce(f,L):
                                  	def _myreduce(f,L,n):
                                  		if len(L) == 0:
                                  			return n
                                  		return _myreduce(f, L[1:], f(n,L[0]))
                                  	return _myreduce(f, L[1:], L[0])
                                  

                                  >>> mymap(lambda x: 2*x, [1,2,3,4,5])
                                  [2, 4, 6, 8, 10]
                                  >>> myreduce(lambda a,b: a-b, [1,2,3,4,5])
                                  -13
                                  

                                  Par contre je ne fais aucune vérification quant au fait que le deuxième argument soit iterable etc...
                                  • Partager sur Facebook
                                  • Partager sur Twitter
                                    28 juin 2011 à 7:34:51

                                    Salut !
                                    Yep, moi aussi ça me démangeait de proposer des versions récursives mais je me suis dit que ce serait un peu too much pour les débutants sur ce topic.

                                    Je suis donc en train de creuser l'idée de faire un second topic, avec d'autres petites problématiques de ce genre comme la curryfication, et l'initiation à la programmation dans un style déclaratif avec des fonctions tail-récursives, qui, quoique limitée en Python (on peut pas faire tout ce qu'on veut avec des lambdas), peut être intéressante à découvrir.

                                    À réfléchir, donc, si quelqu'un s'en sent le courage et a de bonnes idées d'énoncés…
                                    • Partager sur Facebook
                                    • Partager sur Twitter
                                    Zeste de Savoir, le site qui en a dans le citron !
                                      28 juin 2011 à 12:03:52

                                      Faire du tail récursif n'a pas d'intérêt en Python : il ne fait pas d'optimisation dessus, donc on a exactement le même problème qu'avec les fonctions récursives normales.
                                      • Partager sur Facebook
                                      • Partager sur Twitter
                                        28 juin 2011 à 13:09:06

                                        Citation : Orrita

                                        Faire du tail récursif n'a pas d'intérêt en Python : il ne fait pas d'optimisation dessus, donc on a exactement le même problème qu'avec les fonctions récursives normales.


                                        Et tant que Guido dirigera le projet, ça ne le sera probablement jamais.
                                        • Partager sur Facebook
                                        • Partager sur Twitter
                                          28 juin 2011 à 13:14:22

                                          Bonjour,

                                          Merci à nohar pour ce très sympathique exo :) , qui va me permettre de m'entrainer (je pratique peu le python).

                                          Voici mes premiers codes, je m'excuse de ne pas avoir lu les codes proposés ci-dessus en cas de doublon :

                                          my_map(), utilise une compréhension de liste pour condenser le code
                                          def my_map(fn,liste):
                                              return [fn(elt) for elt in liste]
                                          


                                          my_reduce()
                                          def my_reduce(fn,liste,acc):
                                              for elt in liste:
                                                  acc=fn(acc,elt)
                                              return acc
                                          


                                          my_map2(), cette fonction m'a donné nettement plus de mal :-° .
                                          def my_map2(fn,liste):
                                              return reduce(lambda liste,elt: liste+[fn(elt)], liste, [])
                                          


                                          Voilà pour l'instant, tous commentaires bienvenus (pas taper trop fort svp :lol: ).

                                          EDIT :
                                          my_map_gen()
                                          def my_map_gen(fn,iterable):
                                              for elt in iterable:
                                                  yield fn(elt)
                                          


                                          Pour compose(), j'ai fini par craquer et j'ai regardé la solution... :honte:
                                          • Partager sur Facebook
                                          • Partager sur Twitter
                                            28 juin 2011 à 18:31:14

                                            Citation : Orrita

                                            Faire du tail récursif n'a pas d'intérêt en Python : il ne fait pas d'optimisation dessus, donc on a exactement le même problème qu'avec les fonctions récursives normales.


                                            Je suis tout à fait d'accord là-dessus, je voulais plutôt montrer un autre style de solution plutôt qu'une solution performante, le topic de base restant quand même la programmation fonctionnelle je pense que le tail recursive à sa place dans ce topic.
                                            • Partager sur Facebook
                                            • Partager sur Twitter
                                              28 juin 2011 à 18:40:01

                                              Hum, je parlais surtout à NoHaR en fait.
                                              • Partager sur Facebook
                                              • Partager sur Twitter
                                                28 juin 2011 à 20:05:54

                                                Hmm, c'est pas faux finalement.

                                                Il faudra que je trouve quelque-chose de plus pertinent pour aborder les lambdas et la programmation déclarative.
                                                • Partager sur Facebook
                                                • Partager sur Twitter
                                                Zeste de Savoir, le site qui en a dans le citron !
                                                  28 juin 2011 à 20:09:56

                                                  Pour les lambdas, peu importe, il n'y est pas question d'optimisation ou de pile. Et dans tous les cas, on peut parler de récursivité, mais se focaliser sur la récursivité terminale n'a pas d'intérêt avec Python. Et même dans les autres langages, son seul intérêt est de s'affranchir des limites de la pile : fondamentalement, ce n'est pas une notion très importante.
                                                  • Partager sur Facebook
                                                  • Partager sur Twitter
                                                    28 juin 2011 à 21:12:31

                                                    Tout comme les décorateurs et les features fonctionnelles ne sont pas des notions très importantes en Python, si on part par là.

                                                    Visiblement, la seule raison pour laquelle l'ami Guido a laissé map et reduce entrer dans son langage est leur concision et leur puissance. C'est triste, quelque-part, et c'est à se demander à quoi peuvent servir des lambdas (relativement peu de lambdas sont vraiment plus lisibles que leur pendant "def machin(truc):").

                                                    Au final, leur seul intérêt, c'est surtout de pouvoir créer des one-liners là ou ce n'est pas rendu impossible par la limitation à X if P else Y, juste pour se marrer un coup.

                                                    Edit : il suffirait d'un "switch-like", sans forcément qu'il permette de faire du pattern matching (ce qui serait probablement très difficile, voire impossible à intégrer dans le langage tel qu'il est aujourd'hui), pour que l'aspect "programmation fonctionnelle/déclarative" de Python devienne vraiment sexy.
                                                    • Partager sur Facebook
                                                    • Partager sur Twitter
                                                    Zeste de Savoir, le site qui en a dans le citron !
                                                      28 juin 2011 à 21:35:07

                                                      Ce que je veux dire, c'est qu'en Python, la récursivité terminale n'a absolument rien de spécial. Elle est exactement équivalente à une récursivité classique, contrairement aux décorateurs et à map/reduce qui, même si on peut évidemment faire sans, apportent véritablement quelque chose.
                                                      • Partager sur Facebook
                                                      • Partager sur Twitter
                                                        28 juin 2011 à 22:15:00

                                                        Bonjour,

                                                        Histoire de faire vraiment fonctionnel, voici quelques fonction récursives :

                                                        def my_map_rec(fn,liste):
                                                            if liste != []:
                                                                return [fn(liste[0])] + my_map_rec(fn,liste[1:])
                                                            else:
                                                                return []
                                                        
                                                        def my_reduce_rec(fn,liste,acc):
                                                            if liste == []:
                                                                return acc
                                                            else:
                                                                return my_reduce_rec(fn,liste[1:],fn(acc,liste[0]))
                                                        
                                                        # reduce sur un iterable
                                                        def my_reduce_rec_it(fn,iterable,acc):
                                                            try:
                                                                acc = fn(acc,iterable.__next__())
                                                                return my_reduce_rec_it(fn,iterable,acc)
                                                            except StopIteration:
                                                                return acc
                                                        


                                                        Encore une fois, j'aimerais vos commentaires svp. :)

                                                        La dernière fonction pose un problème, en effet je ne peux l'appeler que comme ceci : my_reduce_rec_it(somme,iter([1,2,3,4,5]),0), impossible de lui passer directement une liste. Que faire ?
                                                        • Partager sur Facebook
                                                        • Partager sur Twitter
                                                          28 juin 2011 à 22:25:49

                                                          Tu peux faire encore « à la fonctionnelle » : là, tu utilises une sorte d'effet de bord sur le générateur avec __next__. Tu peux t'en sortir autrement en prenant le premier élément de la liste et en appelant la fonction récursive sur tous les autres : liste[1:]
                                                          • Partager sur Facebook
                                                          • Partager sur Twitter
                                                            28 juin 2011 à 22:36:46

                                                            Citation : Orrita

                                                            Tu peux faire encore « à la fonctionnelle » : là, tu utilises une sorte d'effet de bord sur le générateur avec __next__. Tu peux t'en sortir autrement en prenant le premier élément de la liste et en appelant la fonction récursive sur tous les autres : liste[1:]


                                                            Effectivement (c'est d'ailleurs ce que je fais sur le 2 premiers codes, il me semble), mais je voulais coller un peu plus à la définition de la fonction qui est censée recevoir un iterable et non une liste.

                                                            D'ailleurs, ça ne répond pas à ma question : pourquoi ma fonction prenant un iterable ne fonctionne pas sur une liste, alors que les fonctions standard oui ? En d'autres termes, quel est le mécanisme employé par les fonctions recevant un itérable pour fonctionner avec des listes, sachant que __next__() ne fonctionne visiblement pas sur une liste ?

                                                            S'il n'y a que for pour le faire, difficile de faire du récursif correct sur un iterable...
                                                            • Partager sur Facebook
                                                            • Partager sur Twitter
                                                              28 juin 2011 à 22:50:34

                                                              Les fonctions utilisant quelque chose comme for utilisent la méthode __iter__ des itérables, qui doit renvoyer un générateur.
                                                              • Partager sur Facebook
                                                              • Partager sur Twitter

                                                              [TP] Notions de programmation fonctionnelle

                                                              × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
                                                              × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
                                                              • Editeur
                                                              • Markdown