Verwenden von Python Map und anderen funktionalen Tools

127

Das ist ziemlich n00bish, aber ich versuche, funktionale Programmierung in Python zu lernen / zu verstehen. Der folgende Code:

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)

produziert:

1.0 1
2.0 2
3.0 3
4.0 None
5.0 None

Frage: Gibt es eine Möglichkeit, Map oder andere funktionale Tools in Python zu verwenden, um Folgendes ohne Schleifen usw. zu erzeugen?

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]

Nur als Randnotiz, wie würde sich die Implementierung ändern, wenn es eine Abhängigkeit zwischen foo und bar gibt. z.B

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

und drucken:

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

PS: Ich weiß, wie man es naiv mit if, Schleifen und / oder Generatoren macht, aber ich möchte lernen, wie man dasselbe mit funktionalen Werkzeugen erreicht. Geht es nur darum, eine if-Anweisung zu maptest hinzuzufügen oder eine andere Filterzuordnung auf Balken innerhalb von maptest anzuwenden?

eusoubrasileiro
quelle
Danke Leute. Ich muss zugeben, dass ich versuche, die Konzepte der funktionalen Programmierung durch Python zu lernen.
1
Ein schönes Tutorial dafür hier: dreamsyssoft.com/python-scripting-tutorial/…
Triton Man

Antworten:

54

Der einfachste Weg wäre, nicht barsdie verschiedenen Funktionen zu durchlaufen, sondern direkt darauf zuzugreifen von 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)

Mit Ihrer ursprünglichen maptestFunktion können Sie auch eine Lambda-Funktion verwenden in map:

map((lambda foo: maptest(foo, bars)), foos)
etw
quelle
schlecht, wenn Bars von der Liste kommt
Phyo Arkar Lwin
59
Diese Lösung widerspricht direkt den Prinzipien der funktionalen Programmierung, die das OP lernen möchte. Eine grundlegende Regel bei der funktionalen Programmierung ist, dass Sie jedes Mal, wenn Sie eine Funktion mit denselben Argumenten aufrufen, IMMER dieselbe Ausgabe erhalten. Dies vermeidet ein Vipernnest von Fehlern, die durch einen globalen Status verursacht werden. Da der Maptest von einer externen Definition von Balken abhängt, wird dieses Prinzip gebrochen.
image_doctor
3
Lieber Stapelüberlauf, da Sie Fragen gerne und mäßig stark schließen, warum heben Sie diese Frage nicht als Antwort auf und markieren Sie die richtige Antwort als Antwort? Grüße, uns.
Bahadir Cambel
1
@image_doctor, in FP ist es vollkommen in Ordnung, auf die globale Konstante zuzugreifen (dort als Nullfunktion zu betrachten)
Peter K
1
@ BahadirCambel Stack Overflow-Moderation kann manchmal hartnäckig sein, aber das Häkchen gehört immer und immer zum OP.
wizzwizz4
194

Kennen Sie andere funktionale Sprachen? Versuchen Sie also zu lernen, wie Python funktionale Programmierung ausführt, oder versuchen Sie, etwas über funktionale Programmierung und die Verwendung von Python als Vehikel zu lernen?

Verstehst du auch das Listenverständnis?

map(f, sequence)

ist direkt äquivalent (*) zu:

[f(x) for x in sequence]

Tatsächlich war ich der Meinung map(), dass das Entfernen aus Python 3.0 einmal als redundant geplant war (das ist nicht geschehen).

map(f, sequence1, sequence2)

ist meistens gleichbedeutend mit:

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

(Es gibt einen Unterschied in der Behandlung des Falls, in dem die Sequenzen unterschiedlich lang sind. Wie Sie gesehen haben, wird map()None ausgefüllt, wenn eine der Sequenzen abgelaufen ist, während zip()angehalten wird, wenn die kürzeste Sequenz stoppt.)

Um Ihre spezifische Frage zu beantworten, versuchen Sie, das Ergebnis zu erzielen:

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

Sie können dies tun, indem Sie eine Funktion schreiben, die ein einzelnes Argument verwendet und es druckt, gefolgt von Balken:

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

Alternativ können Sie eine Liste erstellen, die folgendermaßen aussieht:

[bars, bars, bars, ] # etc.

und verwenden Sie Ihren Original-Maptest:

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

Eine Möglichkeit, dies zu tun, besteht darin, die Liste im Voraus explizit zu erstellen:

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

Alternativ können Sie das itertoolsModul einziehen. itertoolsenthält viele clevere Funktionen, mit denen Sie in Python eine funktionale Lazy-Evaluation-Programmierung durchführen können. In diesem Fall möchten wir, dass itertools.repeatdas Argument unbegrenzt ausgegeben wird, wenn Sie darüber iterieren. Diese letzte Tatsache bedeutet, dass wenn Sie Folgendes tun:

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

Sie erhalten eine endlose Ausgabe, da map()so lange weitergearbeitet wird, wie eines der Argumente noch eine Ausgabe erzeugt. Ist itertools.imapjedoch genau wie map(), stoppt aber, sobald die kürzeste iterierbare Stopps.

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

Hoffe das hilft :-)

(*) In Python 3.0 ist das etwas anders. Dort gibt map () im Wesentlichen einen Generatorausdruck zurück.

John Fouhy
quelle
Verstehe ich also richtig, dass es im Gegensatz zur Karte itertools.imap(f, sequence1, sequence2)wirklich gleichbedeutend ist mit [f(x1, x2) for x1, x2 in zip(sequence1, sequence2)]?
Jon Coombs
Wenn ich ein bisschen teste, sehe ich, dass es ein itertools.imap-Objekt zurückgibt, also wäre dies vielleicht eher 'äquivalent':list(itertools.imap(f, sequence1, sequence2))
Jon Coombs
Dies sollte die genehmigte Antwort sein.
Rob Grant
30

Hier ist die Lösung, nach der Sie suchen:

>>> 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])]

Ich würde empfehlen, ein Listenverständnis (den [(x, bars) for x in foos]Teil) gegenüber der Verwendung von map zu verwenden, da dies den Aufwand eines Funktionsaufrufs bei jeder Iteration vermeidet (was sehr bedeutsam sein kann). Wenn Sie es nur in einer for-Schleife verwenden, erhalten Sie bessere Geschwindigkeiten, wenn Sie ein Generatorverständnis verwenden:

>>> 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])

Der Unterschied besteht darin, dass das Generatorverständnis träge geladen ist .

UPDATE Als Antwort auf diesen Kommentar:

Natürlich wissen Sie, dass Sie keine Balken kopieren, alle Einträge sind die gleiche Balkenliste. Wenn Sie also einen von ihnen (einschließlich der Originalbalken) ändern, ändern Sie alle.

Ich nehme an, das ist ein gültiger Punkt. Hierfür gibt es zwei Lösungen, an die ich denken kann. Das effizienteste ist wahrscheinlich so etwas:

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

Da Tupel unveränderlich sind, wird verhindert, dass Balken durch die Ergebnisse dieses Listenverständnisses (oder des Generatorverständnisses, wenn Sie diesen Weg gehen) geändert werden. Wenn Sie wirklich jedes einzelne Ergebnis ändern müssen, können Sie dies tun:

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

Dies kann jedoch sowohl in Bezug auf die Speichernutzung als auch in Bezug auf die Geschwindigkeit etwas teuer sein. Daher würde ich davon abraten, es sei denn, Sie müssen wirklich zu jedem von ihnen etwas hinzufügen.

Jason Baker
quelle
1
Natürlich wissen Sie, dass Sie keine Balken kopieren, alle Einträge sind die gleiche Balkenliste. Wenn Sie also einen von ihnen (einschließlich der Originalbalken) ändern, ändern Sie alle.
Vartec
20

Bei der funktionalen Programmierung geht es darum, nebenwirkungsfreien Code zu erstellen.

map ist eine funktionale Listentransformationsabstraktion. Sie verwenden es, um eine Sequenz von etwas zu nehmen und es in eine Sequenz von etwas anderem zu verwandeln.

Sie versuchen, es als Iterator zu verwenden. Tu das nicht. :) :)

Hier ist ein Beispiel, wie Sie mithilfe der Karte die gewünschte Liste erstellen können. Es gibt kürzere Lösungen (ich würde nur Verständnis verwenden), aber dies hilft Ihnen zu verstehen, was Map ein bisschen besser macht:

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

new_list = map(my_transform, input_list)

Beachten Sie an dieser Stelle, dass Sie nur eine Datenmanipulation durchgeführt haben. Jetzt können Sie es ausdrucken:

for n,l in new_list:
    print n, ll

- Ich bin mir nicht sicher, was Sie unter "ohne Schleifen" verstehen. Bei fp geht es nicht darum, Schleifen zu vermeiden (Sie können nicht jedes Element in einer Liste untersuchen, ohne jedes zu besuchen). Es geht darum, Nebenwirkungen zu vermeiden und so weniger Fehler zu schreiben.

Dustin
quelle
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
quelle
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
quelle
Dies ist die einfachste und korrekt funktionierende. Bitte akzeptieren Sie dies als Antwort
Phyo Arkar Lwin
1
Dies ist nur Code. Es gibt keine Erklärung. Viele Benutzer verstehen nicht, was diese Antwort bedeutet. @PhyoArkarLwin
ProgramFast
6

Hier ist eine Übersicht über die Parameter der map(function, *sequences)Funktion:

  • function ist der Name Ihrer Funktion.
  • sequencesist eine beliebige Anzahl von Sequenzen, bei denen es sich normalerweise um Listen oder Tupel handelt. mapwird sie gleichzeitig durchlaufen und die aktuellen Werte an geben function. Aus diesem Grund sollte die Anzahl der Sequenzen der Anzahl der Parameter Ihrer Funktion entsprechen.

Es hört sich so an, als würden Sie versuchen, einige functionParameter zu iterieren , andere jedoch konstant zu halten, und dies wird leider mapnicht unterstützt. Ich habe einen alten Vorschlag gefunden , Python eine solche Funktion hinzuzufügen, aber das Kartenkonstrukt ist so sauber und gut etabliert, dass ich bezweifle, dass so etwas jemals implementiert wird.

Verwenden Sie eine Problemumgehung wie globale Variablen oder Listenverständnisse, wie andere vorgeschlagen haben.

Nikhil Chelliah
quelle
0

Würde das das tun?

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
quelle
1
Vielleicht möchten Sie den Parameter für maptest2 () so etwas wie 'Bars' aufrufen. Der Singular barimpliziert, dass er einen iterierten Wert erhält, wenn Sie tatsächlich die gesamte Liste möchten.
Nikhil Chelliah
1
Es erhält tatsächlich einen iterierten Wert, glaube ich.
Chris
0

Wie wäre es damit:

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
quelle