Использование карты Python и других функциональных инструментов


127

Это довольно странно, но я пытаюсь изучить / понять функциональное программирование на Python. Следующий код:

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)

производит:

1.0 1
2.0 2
3.0 3
4.0 None
5.0 None

В. Есть ли способ использовать карту или любые другие функциональные инструменты в Python для создания следующего без циклов и т. Д.

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]

Просто в качестве примечания, как изменится реализация, если существует зависимость между foo и bar. например

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

и распечатать:

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

PS: Я знаю, как сделать это наивно, используя if, циклы и / или генераторы, но я хотел бы узнать, как добиться того же с помощью функциональных инструментов. Это просто случай добавления оператора if в maptest или применения другой карты фильтров к барам внутри maptest?


Спасибо, парни. Должен признаться, я пытаюсь изучить концепции функционального программирования через Python.

1
Хороший учебник для этого здесь: dreamsyssoft.com/python-scripting-tutorial/…
Rocky

Ответы:


54

Самый простой способ - не проходить barsчерез различные функции, а получить к ним доступ напрямую из 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)

С вашей исходной maptestфункцией вы также можете использовать лямбда-функцию в map:

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

плохой, когда бары
выбираются

59
Это решение прямо противоречит принципам функционального программирования, которые ОП хочет изучить. Основное правило функционального программирования заключается в том, что каждый раз, когда вы вызываете функцию с одними и теми же аргументами, вы ВСЕГДА получаете один и тот же результат. Это позволяет избежать множества ошибок, вызванных наличием глобального состояния. Поскольку маптест зависит от внешнего определения столбцов, этот принцип нарушен.
image_doctor

3
Уважаемый Stack overflow, так как вам нравится закрывать вопросы и умеренно сильно, почему бы вам не снять отметку с этого вопроса как ответ и не отметить правильный ответ как ответ? С уважением, мы.
Бахадир Камбель

1
@image_doctor, в FP совершенно нормально получить доступ к глобальной константе (там считается нулевой функцией)
Питер К.

1
@BahadirCambel Иногда модерация переполнения стека может быть жесткой, но галочка всегда и всегда будет принадлежать OP.
wizzwizz4

194

Вы знакомы с другими функциональными языками? т.е. вы пытаетесь узнать, как Python выполняет функциональное программирование, или вы пытаетесь узнать о функциональном программировании и использовании Python в качестве средства передвижения?

Кроме того, вы понимаете состав списков?

map(f, sequence)

прямо эквивалентен (*):

[f(x) for x in sequence]

На самом деле, я думаю, что map()когда-то планировалось удалить из python 3.0 как избыточный (этого не произошло).

map(f, sequence1, sequence2)

в основном эквивалентно:

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

(есть разница в том, как он обрабатывает случай, когда последовательности имеют разную длину. Как вы видели, map()заполняется None, когда одна из последовательностей заканчивается, тогда как zip()останавливается, когда останавливается самая короткая последовательность)

Итак, чтобы ответить на ваш конкретный вопрос, вы пытаетесь получить результат:

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

Вы можете сделать это, написав функцию, которая принимает единственный аргумент и печатает его, за которым следуют полосы:

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

В качестве альтернативы вы можете создать список, который будет выглядеть так:

[bars, bars, bars, ] # etc.

и используйте свой исходный маптест:

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

Один из способов сделать это - заранее явно создать список:

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

Как вариант, вы можете вставить itertoolsмодуль. itertoolsсодержит множество умных функций, которые помогут вам выполнять программирование с отложенным вычислением в функциональном стиле на Python. В данном случае мы хотим itertools.repeat, чтобы аргумент выводился бесконечно при повторении по нему. Этот последний факт означает, что если вы это сделаете:

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

вы получите бесконечный вывод, так как map()будет продолжаться до тех пор, пока один из аргументов все еще производит вывод. Тем не менее, itertools.imapэто то же map()самое, но останавливается, как только останавливается самая короткая итерация.

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

Надеюсь это поможет :-)

(*) В python 3.0 все немного по-другому. Там map () по существу возвращает выражение генератора.


Итак, правильно ли я понимаю, что, в отличие от map, itertools.imap(f, sequence1, sequence2)действительно эквивалентно [f(x1, x2) for x1, x2 in zip(sequence1, sequence2)]?
Джон Кумбс

Немного протестировав, я вижу, что он возвращает объект itertools.imap, так что, возможно, это будет более «эквивалентным»:list(itertools.imap(f, sequence1, sequence2))
Джон Кумбс

Это должен быть утвержденный ответ.
Роб Грант

30

Вот решение, которое вы ищете:

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

Я бы рекомендовал использовать понимание списка ( [(x, bars) for x in foos]часть) вместо использования карты, поскольку оно позволяет избежать накладных расходов на вызов функции на каждой итерации (что может быть очень значительным). Если вы просто собираетесь использовать его в цикле for, вы получите лучшую скорость, используя понимание генератора:

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

Разница в том, что понимание генератора загружается лениво .

ОБНОВЛЕНИЕ В ответ на этот комментарий:

Конечно, вы знаете, что вы не копируете бары, все записи - это один и тот же список баров. Поэтому, если вы измените какой-либо из них (включая исходные столбцы), вы измените их все.

Я полагаю, это верная точка зрения. Я могу придумать два решения этой проблемы. Наиболее эффективным, вероятно, будет что-то вроде этого:

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

Поскольку кортежи неизменяемы, это предотвратит изменение полос в результате понимания этого списка (или понимания генератора, если вы пойдете по этому маршруту). Если вам действительно нужно изменить каждый из результатов, вы можете сделать это:

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

Однако это может быть немного дороже как с точки зрения использования памяти, так и с точки зрения скорости, поэтому я бы не рекомендовал его, если вам действительно не нужно добавлять к каждому из них.


1
Конечно, вы знаете, что вы не копируете бары, все записи - это один и тот же список баров. Поэтому, если вы измените какой-либо из них (включая исходные столбцы), вы измените их все.
vartec

20

Функциональное программирование - это создание кода без побочных эффектов.

map - это абстракция функционального преобразования списка. Вы используете его, чтобы взять последовательность чего-то и превратить ее в последовательность чего-то еще.

Вы пытаетесь использовать его как итератор. Не делай этого. :)

Вот пример того, как вы можете использовать map для создания нужного списка. Есть более короткие решения (я бы просто использовал понимание), но это поможет вам понять, какая карта работает немного лучше:

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

new_list = map(my_transform, input_list)

Обратите внимание на то, что на этом этапе вы только манипулировали данными. Теперь его можно распечатать:

for n,l in new_list:
    print n, ll

- Я не совсем понимаю, что вы имеете в виду под словом «без петель». fp - это не избежание циклов (вы не можете изучить каждый элемент в списке, не посещая каждый из них). Речь идет об избежании побочных эффектов, что позволяет писать меньше ошибок.



11
import itertools

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

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

Это самый простой и правильно работающий. пожалуйста, примите это как ответ
Phyo Arkar Lwin

1
Это просто код. Нет никаких объяснений. Многие пользователи не понимают, что означает этот ответ. @PhyoArkarLwin
ProgramFast

6

Вот обзор параметров map(function, *sequences)функции:

  • function это имя вашей функции.
  • sequences- любое количество последовательностей, которые обычно представляют собой списки или кортежи. mapбудет перебирать их одновременно и передавать текущие значения function. Поэтому количество последовательностей должно равняться количеству параметров вашей функции.

Похоже, вы пытаетесь выполнить итерацию для некоторых functionпараметров, но сохранить другие постоянными, и, к сожалению, mapэто не поддерживает. Я нашел старое предложение добавить такую ​​функцию в Python, но конструкция карты настолько чиста и хорошо отработана, что я сомневаюсь, что что-то подобное когда-либо будет реализовано.

Используйте обходной путь, например глобальные переменные или составление списков, как предлагали другие.


0

Будет ли это делать?

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
Возможно, вы захотите вызвать параметр для maptest2 (), например, «бары». Единственное число barподразумевает получение повторяющегося значения, когда вам действительно нужен весь список.
Nikhil Chelliah

1
Я полагаю, что на самом деле он получает повторяющееся значение.
Крис,

0

Как насчет этого:

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))
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.