Utilisation de la carte python et d'autres outils fonctionnels

127

C'est assez n00bish, mais j'essaie d'apprendre / comprendre la programmation fonctionnelle en python. Le code suivant:

foos = [1.0,2.0,3.0,4.0,5.0]
bars = [1,2,3]

def maptest(foo, bar):
    print foo, bar

map(maptest, foos, bars)

produit:

1.0 1
2.0 2
3.0 3
4.0 None
5.0 None

Y a-t-il un moyen d'utiliser map ou tout autre outil fonctionnel en python pour produire ce qui suit sans boucles, etc.

1.0 [1,2,3]
2.0 [1,2,3]
3.0 [1,2,3]
4.0 [1,2,3]
5.0 [1,2,3]

Juste comme note latérale, comment l'implémentation changerait s'il y avait une dépendance entre foo et bar. par exemple

foos = [1.0,2.0,3.0,4.0,5.0]
bars = [1,2,3,4,5]

et imprimez:

1.0 [2,3,4,5]
2.0 [1,3,4,5]
3.0 [1,2,4,5]
...

PS: Je sais comment le faire naïvement en utilisant des if, des boucles et / ou des générateurs, mais j'aimerais apprendre comment réaliser la même chose en utilisant des outils fonctionnels. S'agit-il simplement d'ajouter une instruction if à maptest ou d'appliquer une autre carte de filtre aux barres en interne dans maptest?

eusoubrasileiro
la source
Merci les gars. Je dois admettre que j'essaie d'apprendre les concepts de programmation fonctionnelle via python.
1
Un joli tutoriel ici: dreamyssoft.com/python-scripting-tutorial
Rocky Pulley

Réponses:

54

Le moyen le plus simple serait de ne pas passer barspar les différentes fonctions, mais d'y accéder directement depuis maptest:

foos = [1.0,2.0,3.0,4.0,5.0]
bars = [1,2,3]

def maptest(foo):
    print foo, bars

map(maptest, foos)

Avec votre maptestfonction d' origine , vous pouvez également utiliser une fonction lambda dans map:

map((lambda foo: maptest(foo, bars)), foos)
qc
la source
mauvais quand les barres viennent de la liste
Phyo Arkar Lwin
59
Cette solution va directement à l'encontre des principes de la programmation fonctionnelle que l'OP veut tenter d'apprendre. Une règle fondamentale en programmation fonctionnelle est que chaque fois que vous appelez une fonction avec les mêmes arguments, vous obtenez TOUJOURS la même sortie. Cela évite un nid de vipères de bogues introduits en ayant un état global. Comme le maptest dépend d'une définition externe des barres, ce principe est rompu.
image_doctor
3
Cher Stack overflow, puisque vous aimez fermer les questions et modérément fortement, pourquoi ne pas décocher cette question comme réponse et marquer la bonne réponse comme réponse? Cordialement, nous.
Bahadir Cambel
1
@image_doctor, en FP c'est parfaitement ok pour accéder à la constante globale (là regarder comme fuction nulle)
Peter K
1
La modération @BahadirCambel Stack Overflow peut parfois être lourde, mais la coche appartiendra toujours, et sera toujours, à l'OP.
wizzwizz4
194

Connaissez-vous d'autres langages fonctionnels? c'est-à-dire essayez-vous d'apprendre comment python fait de la programmation fonctionnelle, ou essayez-vous d'apprendre la programmation fonctionnelle et l'utilisation de python comme véhicule?

Aussi, comprenez-vous les compréhensions de listes?

map(f, sequence)

est directement équivalent (*) à:

[f(x) for x in sequence]

En fait, je pense map()que la suppression de python 3.0 était autrefois redondante (cela ne s'est pas produit).

map(f, sequence1, sequence2)

équivaut principalement à:

[f(x1, x2) for x1, x2 in zip(sequence1, sequence2)]

(il y a une différence dans la façon dont il gère le cas où les séquences sont de longueur différente. Comme vous l'avez vu, map()remplit None lorsque l'une des séquences s'épuise, alors que zip()s'arrête lorsque la séquence la plus courte s'arrête)

Donc, pour répondre à votre question spécifique, vous essayez de produire le résultat:

foos[0], bars
foos[1], bars
foos[2], bars
# etc.

Vous pouvez le faire en écrivant une fonction qui prend un seul argument et l'affiche, suivi de barres:

def maptest(x):
     print x, bars
map(maptest, foos)

Vous pouvez également créer une liste qui ressemble à ceci:

[bars, bars, bars, ] # etc.

et utilisez votre maptest d'origine:

def maptest(x, y):
    print x, y

Une façon de faire serait de construire explicitement la liste au préalable:

barses = [bars] * len(foos)
map(maptest, foos, barses)

Vous pouvez également insérer le itertoolsmodule. itertoolscontient de nombreuses fonctions intelligentes qui vous aident à faire de la programmation d'évaluation paresseuse de style fonctionnel en python. Dans ce cas, nous voulons itertools.repeat, qui affichera son argument indéfiniment au fur et à mesure que vous l'itérerez. Ce dernier fait signifie que si vous faites:

map(maptest, foos, itertools.repeat(bars))

vous obtiendrez une sortie sans fin, car map()continue tant que l'un des arguments produit encore une sortie. Cependant, itertools.imapc'est juste comme map(), mais s'arrête dès que l'itérable le plus court s'arrête.

itertools.imap(maptest, foos, itertools.repeat(bars))

J'espère que cela t'aides :-)

(*) C'est un peu différent en python 3.0. Là, map () renvoie essentiellement une expression de générateur.

John Fouhy
la source
Alors, est-ce que je comprends bien que, contrairement à la carte, itertools.imap(f, sequence1, sequence2)est vraiment équivalent à [f(x1, x2) for x1, x2 in zip(sequence1, sequence2)]?
Jon Coombs
En testant un peu, je vois qu'il renvoie un objet itertools.imap, alors peut-être que ce serait plus 'équivalent':list(itertools.imap(f, sequence1, sequence2))
Jon Coombs
Cela devrait être la réponse approuvée.
Rob Grant
30

Voici la solution que vous recherchez:

>>> foos = [1.0, 2.0, 3.0, 4.0, 5.0]
>>> bars = [1, 2, 3]
>>> [(x, bars) for x in foos]
[(1.0, [1, 2, 3]), (2.0, [1, 2, 3]), (3.0, [1, 2, 3]), (4.0, [1, 2, 3]), (5.0, [
1, 2, 3])]

Je recommanderais d'utiliser une compréhension de liste (la [(x, bars) for x in foos]partie) plutôt que d'utiliser la carte car cela évite la surcharge d'un appel de fonction à chaque itération (ce qui peut être très important). Si vous allez simplement l'utiliser dans une boucle for, vous obtiendrez de meilleures vitesses en utilisant une compréhension du générateur:

>>> y = ((x, bars) for x in foos)
>>> for z in y:
...     print z
...
(1.0, [1, 2, 3])
(2.0, [1, 2, 3])
(3.0, [1, 2, 3])
(4.0, [1, 2, 3])
(5.0, [1, 2, 3])

La différence est que la compréhension du générateur est chargée paresseusement .

MISE À JOUR En réponse à ce commentaire:

Bien sûr, vous savez que vous ne copiez pas les barres, toutes les entrées sont la même liste de barres. Donc, si vous modifiez l'une d'entre elles (y compris les barres d'origine), vous les modifiez toutes.

Je suppose que c'est un point valable. Il y a deux solutions à cela auxquelles je peux penser. Le plus efficace est probablement quelque chose comme ceci:

tbars = tuple(bars)
[(x, tbars) for x in foos]

Puisque les tuples sont immuables, cela empêchera les barres d'être modifiées par les résultats de cette compréhension de liste (ou la compréhension du générateur si vous suivez cette voie). Si vous avez vraiment besoin de modifier chacun des résultats, vous pouvez le faire:

from copy import copy
[(x, copy(bars)) for x in foos]

Cependant, cela peut être un peu cher à la fois en termes d'utilisation de la mémoire et de vitesse, je vous le déconseille donc, à moins que vous n'ayez vraiment besoin d'ajouter à chacun d'eux.

Jason Baker
la source
1
Bien sûr, vous savez que vous ne copiez pas les barres, toutes les entrées sont la même liste de barres. Donc, si vous modifiez l'une d'entre elles (y compris les barres d'origine), vous les modifiez toutes.
vartec
20

La programmation fonctionnelle consiste à créer du code sans effets secondaires.

map est une abstraction de transformation de liste fonctionnelle. Vous l'utilisez pour prendre une séquence de quelque chose et la transformer en une séquence de quelque chose d'autre.

Vous essayez de l'utiliser comme itérateur. Ne fais pas ça. :)

Voici un exemple de la façon dont vous pouvez utiliser map pour créer la liste souhaitée. Il existe des solutions plus courtes (j'utiliserais simplement des compréhensions), mais cela vous aidera à comprendre ce que la carte fait un peu mieux:

def my_transform_function(input):
    return [input, [1, 2, 3]]

new_list = map(my_transform, input_list)

Remarquez à ce stade, vous n'avez effectué qu'une manipulation de données. Vous pouvez maintenant l'imprimer:

for n,l in new_list:
    print n, ll

- Je ne suis pas sûr de ce que vous entendez par «sans boucles». fp ne consiste pas à éviter les boucles (vous ne pouvez pas examiner tous les éléments d'une liste sans les visiter). Il s'agit d'éviter les effets secondaires, donc d'écrire moins de bogues.

Dustin
la source
12
>>> from itertools import repeat
>>> for foo, bars in zip(foos, repeat(bars)):
...     print foo, bars
... 
1.0 [1, 2, 3]
2.0 [1, 2, 3]
3.0 [1, 2, 3]
4.0 [1, 2, 3]
5.0 [1, 2, 3]
Roberto Bonvallet
la source
11
import itertools

foos=[1.0, 2.0, 3.0, 4.0, 5.0]
bars=[1, 2, 3]

print zip(foos, itertools.cycle([bars]))
Ignacio Vazquez-Abrams
la source
C'est le plus simple et le plus fonctionnel. Veuillez accepter ceci comme réponse
Phyo Arkar Lwin
1
C'est juste du code. Il n'y a pas d'explication. De nombreux utilisateurs ne comprennent pas ce que signifie cette réponse. @PhyoArkarLwin
ProgramFast
6

Voici un aperçu des paramètres de la map(function, *sequences)fonction:

  • function est le nom de votre fonction.
  • sequencesest un nombre quelconque de séquences, qui sont généralement des listes ou des tuples. mapva les parcourir simultanément et donner les valeurs actuelles à function. C'est pourquoi le nombre de séquences doit être égal au nombre de paramètres de votre fonction.

On dirait que vous essayez d'itérer pour certains functionparamètres de, mais que vous gardez les autres constants, et malheureusement mapne supporte pas cela. J'ai trouvé une vieille proposition pour ajouter une telle fonctionnalité à Python, mais la construction de la carte est si propre et bien établie que je doute que quelque chose comme ça soit jamais implémenté.

Utilisez une solution de contournement telle que des variables globales ou des compréhensions de liste, comme d'autres l'ont suggéré.

Nikhil Chelliah
la source
0

Est-ce que cela le ferait?

foos = [1.0,2.0,3.0,4.0,5.0]
bars = [1,2,3]

def maptest2(bar):
  print bar

def maptest(foo):
  print foo
  map(maptest2, bars)

map(maptest, foos)
Chris
la source
1
Vous voudrez peut-être appeler le paramètre pour maptest2 () quelque chose comme «bars». Le singulier barimplique qu'il reçoit une valeur itérée, alors que vous voulez réellement la liste entière.
Nikhil Chelliah
1
Il reçoit en fait une valeur itérée, je crois.
Chris
0

Que dis-tu de ça:

foos = [1.0,2.0,3.0,4.0,5.0]
bars = [1,2,3]

def maptest(foo, bar):
    print foo, bar

map(maptest, foos, [bars]*len(foos))
sun.huaiyu
la source