Korzystanie z mapy Pythona i innych narzędzi funkcjonalnych


127

To dość nubijne, ale staram się nauczyć / zrozumieć programowanie funkcjonalne w Pythonie. Poniższy kod:

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)

produkuje:

1.0 1
2.0 2
3.0 3
4.0 None
5.0 None

P: Czy istnieje sposób na użycie mapy lub innych narzędzi funkcjonalnych w Pythonie do tworzenia następujących elementów bez pętli itp.

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]

Na marginesie, jak zmieniłaby się implementacja, gdyby istniała zależność między foo i bar. na przykład

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

i wydrukuj:

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

PS: Wiem, jak to zrobić naiwnie, używając if, pętli i / lub generatorów, ale chciałbym się dowiedzieć, jak osiągnąć to samo za pomocą funkcjonalnych narzędzi. Czy jest to tylko przypadek dodania instrukcji if do maptest lub zastosowania innej mapy filtra do słupków wewnętrznie w maptest?


Dzięki chłopaki. Muszę przyznać, że próbuję nauczyć się koncepcji programowania funkcjonalnego poprzez Pythona.

1
Fajny samouczek na ten temat tutaj: dreamsyssoft.com/python-scripting-tutorial/ ...
Rocky Pulley

Odpowiedzi:


54

Najłatwiej byłoby nie przechodzić barsprzez różne funkcje, ale uzyskać do nich bezpośredni dostęp z 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)

W swojej oryginalnej maptestfunkcji możesz również użyć funkcji lambda w map:

map((lambda foo: maptest(foo, bars)), foos)

zły, gdy z listy
wypadają

59
To rozwiązanie jest bezpośrednio sprzeczne z zasadami programowania funkcjonalnego, których PO chce się nauczyć. Podstawową zasadą programowania funkcjonalnego jest to, że za każdym razem, gdy wywołujesz funkcję z tymi samymi argumentami, ZAWSZE otrzymujesz ten sam wynik. W ten sposób unika się gniazda żmijowego robaków wprowadzanych przez posiadanie stanu globalnego. Ponieważ maptest zależy od zewnętrznej definicji prętów, zasada ta jest złamana.
image_doctor

3
Drogi przepełnieniu stosu, ponieważ lubisz zamykać pytania i umiarkowanie intensywnie, dlaczego nie odznaczysz tego pytania jako odpowiedzi i nie zaznaczysz poprawnej odpowiedzi jako odpowiedzi? Pozdrawiam nas.
Bahadir Cambel

1
@image_doctor, w FP jest całkowicie ok, aby uzyskać dostęp do stałej globalnej (w tym przypadku jako funkcję zerową)
Peter K

1
@BahadirCambel Moderacja przepełnienia stosu może być czasami trudna, ale znacznik wyboru zawsze i zawsze będzie należał do OP.
wizzwizz4

194

Czy znasz inne języki funkcjonalne? tj. czy próbujesz się nauczyć, jak Python działa w programowaniu funkcjonalnym, czy też próbujesz nauczyć się programowania funkcjonalnego i używać Pythona jako pojazdu?

Czy rozumiesz też listy ze zrozumieniem?

map(f, sequence)

jest bezpośrednio równoważne (*) z:

[f(x) for x in sequence]

W rzeczywistości myślę, że map()kiedyś był przeznaczony do usunięcia z Pythona 3.0 jako zbędny (tak się nie stało).

map(f, sequence1, sequence2)

jest przeważnie równoważne z:

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

(istnieje różnica w sposobie postępowania w przypadku, gdy sekwencje mają różną długość. Jak widzieliście, map()wypełnia wartość None, gdy skończy się jedna z sekwencji, a zip()zatrzymuje się, gdy najkrótsza sekwencja się zatrzymuje)

Tak więc, odpowiadając na swoje konkretne pytanie, próbujesz uzyskać wynik:

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

Możesz to zrobić, pisząc funkcję, która pobiera pojedynczy argument i wyświetla go, po którym następują paski:

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

Alternatywnie możesz utworzyć listę, która wygląda następująco:

[bars, bars, bars, ] # etc.

i użyj swojego oryginalnego testu map:

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

Jednym ze sposobów na zrobienie tego byłoby wyraźne utworzenie listy wcześniej:

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

Alternatywnie możesz pobrać itertoolsmoduł. itertoolszawiera wiele sprytnych funkcji, które pomagają w programowaniu z leniwą oceną w stylu funkcjonalnym w Pythonie. W tym przypadku chcemy itertools.repeat, aby jego argument był wyświetlany w nieskończoność podczas iteracji. Ten ostatni fakt oznacza, że ​​jeśli:

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

otrzymasz niekończące się dane wyjściowe, ponieważ map()trwa tak długo, jak długo jeden z argumentów nadal generuje wynik. Jednak itertools.imapjest podobny map(), ale zatrzymuje się, gdy tylko najkrótsze iterowalne zatrzymania.

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

Mam nadzieję że to pomoże :-)

(*) Trochę inaczej jest w Pythonie 3.0. Tam map () zasadniczo zwraca wyrażenie generatora.


Czy dobrze rozumiem, że w przeciwieństwie do mapy, itertools.imap(f, sequence1, sequence2)naprawdę jest równoważne [f(x1, x2) for x1, x2 in zip(sequence1, sequence2)]?
Jon Coombs

Trochę testując, widzę, że zwraca obiekt itertools.imap, więc może to byłoby bardziej list(itertools.imap(f, sequence1, sequence2))
Jon Coombs

To powinna być zatwierdzona odpowiedź.
Rob Grant

30

Oto rozwiązanie, którego szukasz:

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

Zalecałbym używanie list złożonych ( [(x, bars) for x in foos]części) zamiast używania mapy, ponieważ pozwala to uniknąć narzutu wywołania funkcji przy każdej iteracji (co może być bardzo znaczące). Jeśli zamierzasz używać go tylko w pętli for, uzyskasz lepsze prędkości, używając funkcji rozumienia generatora:

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

Różnica polega na tym, że rozumienie generatora jest leniwie ładowane .

UPDATE W odpowiedzi na ten komentarz:

Oczywiście wiesz, że nie kopiujesz słupków, wszystkie wpisy to ta sama lista słupków. Więc jeśli zmodyfikujesz którykolwiek z nich (w tym oryginalne paski), zmodyfikujesz je wszystkie.

Przypuszczam, że to ważny punkt. Są dwa rozwiązania, które przychodzą mi do głowy. Najbardziej wydajne jest chyba coś takiego:

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

Ponieważ krotki są niezmienne, zapobiegnie to modyfikowaniu słupków w wyniku zrozumienia tej listy (lub zrozumienia przez generator, jeśli pójdziesz tą drogą). Jeśli naprawdę potrzebujesz zmodyfikować każdy z wyników, możesz to zrobić:

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

Jednak może to być trochę kosztowne zarówno pod względem zużycia pamięci, jak i szybkości, więc odradzam to, chyba że naprawdę musisz dodać do każdego z nich.


1
Oczywiście wiesz, że nie kopiujesz słupków, wszystkie wpisy to ta sama lista słupków. Więc jeśli zmodyfikujesz którykolwiek z nich (w tym oryginalne paski), zmodyfikujesz je wszystkie.
vartec

20

Programowanie funkcjonalne polega na tworzeniu kodu wolnego od efektów ubocznych.

mapa jest abstrakcją transformacji listy funkcjonalnej. Używasz go, aby wziąć sekwencję czegoś i przekształcić ją w sekwencję czegoś innego.

Próbujesz użyć go jako iteratora. Nie rób tego. :)

Oto przykład tego, jak możesz użyć mapy do zbudowania listy, którą chcesz. Istnieją krótsze rozwiązania (użyłbym tylko wyrażeń), ale to pomoże ci zrozumieć, która mapa działa trochę lepiej:

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

new_list = map(my_transform, input_list)

Zauważ, że w tym momencie dokonałeś tylko manipulacji danymi. Teraz możesz go wydrukować:

for n,l in new_list:
    print n, ll

- Nie jestem pewien, co masz na myśli mówiąc „bez pętli”. fp nie polega na unikaniu pętli (nie możesz sprawdzić każdej pozycji na liście bez odwiedzenia każdej z nich). Chodzi o unikanie efektów ubocznych, a tym samym pisanie mniej błędów.


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]

11
import itertools

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

print zip(foos, itertools.cycle([bars]))

To jest najłatwiejsze i poprawnie działające. proszę przyjąć to jako odpowiedź
Phyo Arkar Lwin

1
To tylko kod. Nie ma żadnego wyjaśnienia. Wielu użytkowników nie rozumie, co oznacza ta odpowiedź. @PhyoArkarLwin
ProgramFast

6

Oto przegląd parametrów map(function, *sequences)funkcji:

  • function to nazwa twojej funkcji.
  • sequencesto dowolna liczba sekwencji, które zwykle są listami lub krotkami. mapbędzie iterował po nich jednocześnie i poda bieżące wartości function. Dlatego liczba sekwencji powinna być równa liczbie parametrów twojej funkcji.

Wygląda na to, że próbujesz wykonać iterację dla niektórych functionparametrów, ale inne zachowujesz niezmienione, i niestety maptego nie obsługuje. Znalazłem starą propozycję dodania takiej funkcji do Pythona, ale konstrukcja mapy jest tak czysta i dobrze ugruntowana, że ​​wątpię, aby coś takiego kiedykolwiek zostało zaimplementowane.

Zastosuj obejście, takie jak zmienne globalne lub listy składane, zgodnie z sugestiami innych osób.


0

Czy to by to zrobiło?

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)

1
Możesz chcieć wywołać parametr dla maptest2 () coś w rodzaju „bars”. Liczba pojedyncza baroznacza, że ​​otrzymuje iterowaną wartość, gdy w rzeczywistości chcesz mieć całą listę.
Nikhil Chelliah

1
Wierzę, że faktycznie otrzymuje iterowaną wartość.
Chris

0

Co powiesz na to:

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))
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.