Використання карти python та інших функціональних інструментів


127

Це досить n00bish, але я намагаюся вивчити / зрозуміти функціональне програмування в 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 Pulley

Відповіді:


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
Шановний переповнення стека, оскільки ви любите закривати запитання і помірно сильно, чому б ви не позначили це питання як відповідь і не позначили правильну відповідь як відповідь? З повагою до нас.
Бахадір Камбел

1
@image_doctor, у FP - це цілком нормально, щоб отримати доступ до глобальної константи (там, хто вважає нуклеарне злиття)
Peter K

1
@BahadirCambel Модерування переповнення стека іноді може бути важкою, але галочка завжди, і завжди, належить до ОП.
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 () по суті повертає вираз генератора.


Отже, я правильно розумію, що на відміну від карти 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 - це абстракція перетворення функціонального списку. Ви використовуєте його, щоб взяти послідовність чогось і перетворити його на послідовність чогось іншого.

Ви намагаєтесь використовувати його як ітератор. Не робіть цього. :)

Ось приклад того, як ви можете використовувати карту для складання потрібного списку. Є більш короткі рішення (я б просто застосував розуміння), але це допоможе вам зрозуміти, що карта трохи краще:

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

Це найпростіший і правильно функціональний. прийміть це як відповідь
Фьо Аркар Лвін

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означає, що він отримує ітераційне значення, коли ви хочете весь список.
Нікхіл Челлі

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.