Рубрики
Без рубрики

Плоская карта в Python 🐍

Некоторое время назад одна из моих коллег Java с работы спросила: Как вы делаете плоскую карту в Python? Хорошо … Помечено функционально, Python, производительность, советы.

Некоторое время назад одна из моих коллег Java с работы спросила:

Как вы делаете плоскую карту в Python?

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

Какой самый эффективный способ сделать плоскую карту в Python?

Определение

Давайте начнем с определения. Плоская карта – это операция, которая принимает список, какие элементы имеют тип А и функция F типа A -> [B] Отказ Функция F затем применяется к каждому элементу исходного списка, а затем все результаты объединяются. Так тип flat_map является:

flat_map :: (t -> [a]) -> [t] -> [a]

Я думаю, что показывает пример намного проще, чем описание его:

id = lambda x: x
flat_map(id, [[1,2], [3,4]]) == [1, 2, 3, 4]

s = lambda x: split(x, ";")
flat_map(s, ["a,b", "c,d"]) == ["a", "b", "c", "d"]

Это действительно простая операция, хотя, хотя общая идея за ней является изумительным и называется монад Отказ Насколько я знаю, есть четыре простых способа реализации flat_map В Python:

  • для петли и продления
  • Двойное понимание списка
  • карта и уменьшить
  • карта и сумма

Мера

Какой подход является наиболее эффективным? Чтобы ответить на этот вопрос, я собираюсь измерить время выполнения каждой реализации в 3 случаях:

а) 100 списков каждый из 10 целых чисел б) 10 000 списков каждый с 10 целом C) 10 000 списков каждый с 10 объектами (экземпляры классов)

Для этого я буду использовать это простое check_time Функция и идентичность как функция, нанесенная плоской картой:

import timeit

def id(x):
    return x

def check_time(f, arg):
    n = 50
    return 1000 * (timeit.timeit(lambda: f(id, arg), number=n) / n)  # ms

Все код, который я использовал для этой статьи, доступно как суть .

Уменьшение карты

Если есть «самый функциональный» ответ, то это комбинация карта и Уменьшить (также известен как сгиба). Это требует импорта из functools (Если вы не знаете этого Пакет или упомянутая функция Проверьте это!) Так что это не настоящий одноклассник.

from functools import reduce

flat_map = lambda f, xs: reduce(lambda a, b: a + b, map(f, xs))

Результат: а) 0.1230 мс b) 1202.4405 MS C) 3249.0119 мс

Работа карты может быть заменена соположением списка, но приведет к отсутствию эффектного улучшения.

Сумма карты

Другим функциональным раствором является использование сумма . Однако мне действительно нравится этот подход, его производительность ухудшается с длиной входного списка. А при приложении к ненгенерированным типам ужасно медленно. Результаты не лучше, чем предыдущие.

flat_map = lambda f, xs: sum(map(f, xs), [])

Результат: а) 0.1080 мс Б) 1150,4400 мс c) 3296.3167 MS.

Для и продлить

До сих пор наше flat_map Реализации были довольно хороши, при нанесении относительно небольших входов и паршива на 10 000 списков длины. К счастью, эта «классическая» реализация до 1000X быстрее! Единственный недостаток этого решения – это количество линий, которые необходимо. Но определенно это хорошая цена за действительно улучшенную производительность.

def flat_map(f, xs):
    ys = []
    for x in xs:
        ys.extend(f(x))
    return ys

Результат: а) 0,0211 мс b) 2.4535 MS C) 2.7347 MS

Интересная вещь – это разница между применением этого flat_map к int и класс Списки уже не так отчетливо большие.

Список понимания

Это действительно питонский подход (а также функциональный). Минус этой реализации является его ясность. Я всегда должен проверить, какой правильный порядок для в двойном понимании. Конечно, это просто нит.

flat_map = lambda f, xs: [y for ys in xs for y in f(ys)]

Результат: а) 0,0372 мс b) 4.1477 MS C) 4.5945 MS

Как мы видим, это действительно хорошо работает! Слегка медленнее, чем предыдущая реализация, но понимание списка – одноклассник! Кто-то может даже предлагать переключение понимания списка к выражению генератора:

flat_map = lambda f, xs: (y for ys in xs for y in f(ys))

Результат: а) 0,0008 мс Б) 0,0008 мс С) 0,0007 мс

Еще лучше, а? Конечно, использование выражения генератора применимо только тогда, когда мы собираемся повторять результат. Итак, чтобы проверить его реальную производительность, мы должны измерить код, похожий на этот:

ys = flat_map(id, xs)
for y in ys:
    pass

Тогда мы получаем следующий результат понимание : а) 0,0537 мс б) 5.3700 мс в) 5,7781 мс

Генератор : а) 0.1512 мс б) 13,0335 мс c) 13,3203 мс

Таким образом, первоначальное улучшение не было по-настоящему, так как ничего не было выполнено. Это показывает, что при измерении производительности мы всегда должны помнить об контексте, в котором используется код.

Резюме

уменьшение карты 0,1230 мс. 1202.4405 3249.0119 мс
Сумма карты 0,1080 мс 1150,4400 мс 3296.3167 MS.
для + продлить 0,0211 мс. 2,4535 мс. 2.7347 MS MS.
Двойное понимание списка 0,0372 мс. 4.1477 мс. 4,5945 мс.

Простая проблема, многие решения, но только один победитель … или два из них. Я думаю, что это разумно предоставить первое место для оба цикла + расширяться и понимание списка. Первый отличный, когда производительность – ваша главная проблема. Второй – действительно хороший выбор, когда вам нужен удобный одноклассник.

Поэтому в следующий раз вам нужно будет применить быстрый flat_map В Python просто используйте список пометков. И самое главное, не соблазнится использовать Уменьшить или сумма Для списков, которые являются относительно длинными!

Оригинал: “https://dev.to/turbaszek/flat-map-in-python-3g98”