Usando o mapa python e outras ferramentas funcionais

127

Isso é bastante complicado, mas estou tentando aprender / entender programação funcional em python. O código a seguir:

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)

produz:

1.0 1
2.0 2
3.0 3
4.0 None
5.0 None

P. Existe uma maneira de usar map ou qualquer outra ferramenta funcional em python para produzir o seguinte sem loops, 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]

Apenas como uma observação lateral, como a implementação mudaria se houvesse uma dependência entre foo e bar. por exemplo

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

e imprima:

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

PS: Sei como fazê-lo ingenuamente usando if, loops e / ou geradores, mas gostaria de aprender como obter o mesmo usando ferramentas funcionais. É apenas um caso de adicionar uma instrução if ao maptest ou aplicar outro mapa de filtro às barras internamente no maptest?

EUSOU brasileiro
fonte
Obrigado rapazes. Devo admitir que estou tentando aprender os conceitos de programação funcional através de python.
1
Um bom tutorial para ele aqui: dreamsyssoft.com/python-scripting-tutorial/...
Rocky Pulley

Respostas:

54

A maneira mais fácil seria não passar barspelas diferentes funções, mas acessá-lo diretamente de 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)

Com sua maptestfunção original , você também pode usar uma função lambda em map:

map((lambda foo: maptest(foo, bars)), foos)
sth
fonte
ruim quando bares vem da lista
Phyo Arkar Lwin
59
Essa solução vai diretamente contra os princípios de programação funcional que o OP quer tentar aprender. Uma regra fundamental na programação funcional é que toda vez que você chama uma função com os mesmos argumentos, você SEMPRE obtém a mesma saída. Isso evita um ninho de erros de víboras, introduzido por ter estado global. Como o maptest depende de uma definição externa de barras, esse princípio é quebrado.
Image_doctor
3
Caro Estouro de pilha, como você deseja encerrar as perguntas com moderação, por que você não desmarca esta pergunta como resposta e marca a resposta correta como resposta? Atenciosamente, nós.
Bahadir Cambel
1
@image_doctor, em FP é perfeitamente ok para acessar constante global (há regarder como fuction nullary)
Peter K
1
Às vezes, a moderação do @BahadirCambel Stack Overflow pode ser pesada, mas a marca de seleção sempre e sempre pertence ao OP.
Wizzwizz4
194

Você conhece outras linguagens funcionais? ou seja, você está tentando aprender como o python faz a programação funcional, ou você está tentando aprender sobre a programação funcional e usando o python como veículo?

Além disso, você entende a compreensão da lista?

map(f, sequence)

é diretamente equivalente (*) a:

[f(x) for x in sequence]

Na verdade, acho que map()antes a remoção do python 3.0 estava programada como redundante (isso não aconteceu).

map(f, sequence1, sequence2)

é basicamente equivalente a:

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

(há uma diferença em como ele lida com o caso em que as seqüências são de comprimento diferente. Como você viu, map()preenche Nenhum quando uma das seqüências acaba, enquanto zip()para quando a sequência mais curta é interrompida)

Portanto, para abordar sua pergunta específica, você está tentando produzir o resultado:

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

Você pode fazer isso escrevendo uma função que pega um único argumento e o imprime, seguido por barras:

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

Como alternativa, você pode criar uma lista parecida com esta:

[bars, bars, bars, ] # etc.

e use seu maptest original:

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

Uma maneira de fazer isso seria criar explicitamente a lista de antemão:

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

Como alternativa, você pode puxar o itertoolsmódulo. itertoolscontém muitas funções inteligentes que ajudam a executar a programação de avaliação lenta no estilo funcional em python. Nesse caso, queremos itertools.repeat, que produzirá seu argumento indefinidamente à medida que você iterar sobre ele. Este último fato significa que se você fizer:

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

você obterá uma saída sem fim, pois map()continua enquanto um dos argumentos ainda estiver produzindo saída. No entanto, itertools.imapé como map(), mas para assim que o menor iterável para.

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

Espero que isto ajude :-)

(*) É um pouco diferente no python 3.0. Lá, map () essencialmente retorna uma expressão de gerador.

John Fouhy
fonte
Então, estou entendendo corretamente que, ao contrário do mapa, itertools.imap(f, sequence1, sequence2)é realmente equivalente a [f(x1, x2) for x1, x2 in zip(sequence1, sequence2)]?
Jon Coombs
Testando um pouco, vejo que ele retorna um objeto itertools.imap, então talvez isso seja mais 'equivalente':list(itertools.imap(f, sequence1, sequence2))
Jon Coombs
Essa deve ser a resposta aprovada.
Rob Grant
30

Aqui está a solução que você está procurando:

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

Eu recomendo o uso de uma compreensão de lista (a [(x, bars) for x in foos]parte) sobre o uso do mapa, pois evita a sobrecarga de uma chamada de função em todas as iterações (o que pode ser muito significativo). Se você for usá-lo em um loop for, obterá melhores velocidades usando a compreensão de um gerador:

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

A diferença é que a compreensão do gerador é carregada preguiçosamente .

ATUALIZAÇÃO Em resposta a este comentário:

Claro que você sabe que não copia barras, todas as entradas são da mesma lista de barras. Portanto, se você modificar qualquer um deles (incluindo barras originais), modifique todos eles.

Suponho que este é um ponto válido. Existem duas soluções para isso que posso pensar. O mais eficiente é provavelmente algo como isto:

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

Como as tuplas são imutáveis, isso impedirá que as barras sejam modificadas pelos resultados desta compreensão da lista (ou compreensão do gerador, se você seguir esse caminho). Se você realmente precisar modificar todos e cada um dos resultados, poderá fazer o seguinte:

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

No entanto, isso pode ser um pouco caro, tanto em termos de uso da memória quanto em velocidade, portanto, eu recomendaria isso, a menos que você realmente precise adicionar a cada um deles.

Jason Baker
fonte
1
Claro que você sabe que não copia barras, todas as entradas são da mesma lista de barras. Portanto, se você modificar qualquer um deles (incluindo barras originais), modifique todos eles.
vartec 23/03/09
20

A programação funcional é sobre a criação de código sem efeitos colaterais.

map é uma abstração de transformação de lista funcional. Você o usa para pegar uma sequência de algo e transformá-lo em uma sequência de algo mais.

Você está tentando usá-lo como um iterador. Não faça isso. :)

Aqui está um exemplo de como você pode usar o mapa para criar a lista que deseja. Existem soluções mais curtas (eu usaria apenas compreensões), mas isso ajudará você a entender o que o mapa faz um pouco melhor:

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

new_list = map(my_transform, input_list)

Observe que, neste ponto, você fez apenas uma manipulação de dados. Agora você pode imprimi-lo:

for n,l in new_list:
    print n, ll

- Não sei bem o que você quer dizer com 'sem loops'. O FP não é para evitar loops (você não pode examinar todos os itens de uma lista sem visitar cada um). Trata-se de evitar efeitos colaterais, escrevendo menos erros.

Dustin
fonte
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
fonte
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
fonte
Este é o mais fácil e corretamente funcional. por favor, aceite isso como resposta
Phyo Arkar Lwin
1
Isso é apenas código. Não há explicação. Muitos usuários não entendem o que essa resposta significa. @PhyoArkarLwin
ProgramFast
6

Aqui está uma visão geral dos parâmetros para a map(function, *sequences)função:

  • function é o nome da sua função.
  • sequencesé qualquer número de sequências, que geralmente são listas ou tuplas. mapiterará sobre eles simultaneamente e fornecerá os valores atuais function. É por isso que o número de sequências deve ser igual ao número de parâmetros da sua função.

Parece que você está tentando repetir alguns dos functionparâmetros, mas mantém os outros constantes e, infelizmente map, não suporta isso. Encontrei uma proposta antiga para adicionar esse recurso ao Python, mas a construção do mapa é tão limpa e bem estabelecida que duvido que algo assim jamais seja implementado.

Use uma solução alternativa, como variáveis ​​globais ou compreensões de lista, como outros sugeriram.

Nikhil Chelliah
fonte
0

Isso faria isso?

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
fonte
1
Você pode chamar o param para maptest2 () como algo como 'bars'. O singular barimplica que ele está recebendo um valor iterado, quando você realmente deseja a lista inteira.
Nikhil Chelliah
1
Na verdade, ele está recebendo um valor iterado, acredito.
Chris
0

Que tal agora:

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
fonte