Функция map() в python. Обработка повторяющихся элементов без цикла

Функция map() в python. Обработка повторяющихся элементов без цикла

Денис Ястребов

Функция map() в Python — это встроенный инструмент, который позволяет работать с каждым элементом в итерируемом объекте, преобразовывая их без явного использования цикла for. Этот метод, также известный как отображение, широко применяется для обработки данных. map() особенно полезна, когда требуется применить заданную функцию к каждому элементу коллекции и получить на выходе новый набор данных. Это один из ключевых инструментов, поддерживающих функциональный подход к программированию в Python.


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

Подписаться


К слову о функциональном программировании:

Функциональное программирование основывается на использовании чистых функций, которые принимают аргументы и возвращают значения без изменения исходных данных и состояния программы. Такой подход облегчает разработку, тестирование и понимание кода. Основные методы включают отображение (применение функции к каждому элементу списка), фильтрацию (отбор элементов по условию) и сокращение (сведение списка к одному значению). Несмотря на влияние императивных языков на Python, сообщество запросило и получило функциональные возможности, такие как функции map(), filter() и reduce(), которые стали важными элементами функционального стиля программирования в Python.

Если вы хотите боль узнать о функциональном программировании, предлагаю посетить следующую страницу

Как работает map()

Функция map() поставляется вместе с языком python и мы можем вызвать ее в любой части кода без дополнительных импортов. Функция принимает несколько аргументов:

  • Первый аргумент всегда функция. Важно, что map() самостоятельно вызывает переданную функцию, поэтому ее нужно передать как отложенную, без круглых скобок
  • Второй аргумент - это всегда итерируемый объект. Таких объектов может быть несколько, но мы начнем изучать map() с передачи одного объекта

Сигнатура вызова выглядит следующим образом:

map(function, iterable_object)

map() применяет function к каждому элементу iterable_object и возвращает <map object>, который можно преобразовать в необходимый итерируемый объект. Аргумент function может быть любой функцией (встроенной, импортируемой, самодельной), количество аргументов которой будет равно количеству передаваемых итерируемых объектов.

Единственное, нужно понимать, если вы до этого преобразовывали список, то вы не сможете преобразовать <map object> например в dict

Что бы продемонстрировать наглядный пример, представим что вы решаете задачу и вам с помощью input() нужно ввести N чисел через пробел и преобразовать полученную строку в список. Но в ходе решения нам нужно работать с типом int, а мы имеем list(str). Как бы мы вышли из этой ситуации с помощью цикла:

# В инпут вводим '1 2 3 4'
str_numbers = input().split() # ['1', '2', '3', '4']

int_numbers = []

for num in str_numbers:
  int_numbers.append(int(num))

print(int_numbers) # [1, 2, 3, 4]

Этот код мы можем сократить на 3 строки с помощью map():

# В инпут вводим '1 2 3 4'
numbers = list(map(int, input().split()))

print(numbers) # [1, 2, 3, 4]

Здесь мы передали первым аргументом функцию int, а вторым аргументом передали функцию input. Так как input возвращает строку, мы можем вызвать у нее метод split, после чего и получим список строк. В итоге вторым аргументом мы передаём список, содержащий строки. В простом варианте это будет выглядеть так:

numbers = list(map(int, ['1', '2', '3', '4']))

В итоге map() возвращает нам объект <map object>, который мы преобразовываем в список и получаем новый список с числами, без изменения старого списка со строками.

[INFO]

Поскольку функция map() реализована на языке C и обладает высокой степенью оптимизации, её внутренние циклы могут работать быстрее, чем традиционные циклы for в Python. Это одно из ключевых преимуществ использования map().

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

[INFO]

Собственные функции в виде аргумента

В map() можно передавать не только встроенные функции, но и любые другие функции. Представим, что у нас есть список с ценами в долларах на что-то. Мы хотим перевести всё это в рубли по текущему курсу, преобразовать каждое число в строку и прибавить к каждой строке знак рубля.

def dollars_to_rubles(value):
    return f'{value * 92} ₽'


prices_in_dollars = [13, 41, 7, 16, 23]

prices = list(map(dollars_to_rubles, prices_in_dollars))

print(prices) # ['1196 ₽', '3772 ₽', '644 ₽', '1472 ₽', '2116 ₽']

Мы создали свою функцию. Заметьте, что в параметрах функции передаётся только один аргумент, который будет определять каждое значение по порядку в переданном итерируемом объекте. Если мы передадим два или больше аргументов в нашу функцию, то получим ошибку:

TypeError: function() missing 1 required positional argument: 'arg'

Помним, что количество аргументов для передаваемой в map() функции должно быть равно количеству итерируемых объектов передаваемых в map()

Если передаваемая в map() функция нужна только в конкретном случае, можно использовать lambda выражение и сократить код:

prices_in_dollars = [13, 41, 7, 16, 23]

prices = list(map(lambda value: f'{value * 92} ₽', prices_in_dollars))

print(prices) # ['1196 ₽', '3772 ₽', '644 ₽', '1472 ₽', '2116 ₽']

Обработка более одного итерируемого объекта

Давайте сначала посмотрим на пример:

numbers = [2, 3, 4, 5]
degrees = [6, 5, 4, 3, 2]

result = list(map(pow, numbers, degrees))

print(result) # [64, 243, 256, 125]

Если не знаете, pow принимает два аргумента типа int и возводит первый аргумент в степень со значением второго аргумента.

В последнем примере происходит следующее: аргументами переданной в map() функции становятся элементы переданных итерируемых объектов. Порядок аргументов переданной функции определяется порядком переданных объектов. То есть числа из списка numbers возводятся в степени, значения которых лежат в списке degrees.

Обратите внимание, что в примере один список короче другого. В таком случае количество всех итераций будет соответствовать длине самого короткого списка - len(numbers).

А что если нам нужно обработать 5 списков?

У map() нет явного ограничения в количестве передаваемых итерируемых объектов. Главное что бы их количество совпадало с количеством аргументов передаваемой функции. Но что если количество объектов может меняться и не имеет ограничения по количеству? Давайте рассмотрим следующий код:

def average(*args):
    count = 0
    num = 0
    for n in args:
        num += n
        count += 1

    return round(num / count)


numbers1 = [7, 3, 1, 9, 5]
numbers2 = [3, 56, 7, 2, 213]
numbers3 = [3, 3, 3, 3, 3]
numbers4 = [12, 33, 43, 53, 66]
numbers5 = [9, 1, 6, 5, 6]

result = list(map(average, numbers1, numbers2, numbers3, numbers4, numbers5))

print(result)

Функция average принимает любое количество аргументов, находит среднее значение и возвращает округленный результат. Количество аргументов функции всегда равно количеству итерируемых объектов. Следовательно, при такой конфигурации map() может принимать любое количество итерируемых объектов.

Функция starmap(), старший брат map()

Фантазируем иную, но довольно затертую ситуацию. У нас есть список, в котором лежат кортежи с числами - [(1, 2), (3, 4)]. Нам нужно с числами каждого кортежа сделать следующее: tuple[0] ** tuple[1]. Что бы использовать map(), нужно сначала имеющийся список преобразовать в два списка, где в первом списке будут все числа из кортежей под индексом ноль, а во втором списке все числа под индексом один.

tuples = [(2, 3), (4, 5)]

numbers = []
degrees = []

for t in tuples:
    numbers.append(t[0])
    degrees.append(t[1])

result = list(map(pow, numbers, degrees))

print(result) # [8, 1024]

К слову вместо картежей могут быть списки, или даже словари.

Получается довольно громоздко. Давайте воспользуемся функцией starmap():

from itertools import starmap

tuples = [(2, 3), (4, 5)]

result = list(starmap(pow, tuples))

print(result) # [8, 1024]

Логика сократилась с 7 до 2х строк.

Внутри, starmap(), рассматривает элементы кортежей внутри списка tuples как последовательность аргументов для передаваемой функции. Для функции starmap() действуют те же ограничения по количеству аргументов в передаваемой функции. То есть количество аргументов должно быть равно количеству передаваемых итерируемых объектов. В последнем примере, если в каждом кортеже будет 3 числа - то код отработает полностью, так как функция pow принимает до трёх аргументов. Но вот если кортежи будут содержать по 4 числа, код "падает" с ошибкой:

TypeError: pow() takes at most 3 arguments (4 given)

На последок вспомним пример, где мы могли передавать неограниченное количество итерируемых объектов в map(). Представим что есть матрица:

numbers = [
  [7, 3, 1, 9, 5],
  [3, 56, 7, 2, 213],
  [3, 3, 3, 3, 3],
  [12, 33, 43, 53, 66],
  [9, 1, 6, 5, 6]
]

Мы хотели бы найти среднее значение каждого списка/ряда. Просто map() без дополнительных манипуляций здесь не поможет. Преобразуем старый пример:

from itertools import starmap

def average(*args):
    count = 0
    num = 0
    for n in args:
        num += n
        count += 1

    return round(num / count)


numbers = [
  [7, 3, 1, 9, 5],
  [3, 56, 7, 2, 213],
  [3, 3, 3, 3, 3],
  [12, 33, 43, 53, 66],
  [9, 1, 6, 5, 6]
]

result = list(starmap(average, numbers))

print(result) # [5, 56, 3, 41, 5]

А если мы хотели бы найти среднее значение каждого столбца? Как будто было бы удобно распаковать матрицу и использовать map(). Но если внутри numbers будет 1000 списков? Операция станет неудобной. Мы можем применить немного другой подход, на нюансах которого не будем заострять внимание в этой статье.

from itertools import starmap

def average(*args):
    count = 0
    num = 0
    for n in args:
        num += n
        count += 1

    return round(num / count)


numbers = [
  [7, 3, 1, 9, 5],
  [3, 56, 7, 2, 213],
  [3, 3, 3, 3, 3],
  [12, 33, 43, 53, 66],
  [9, 1, 6, 5, 6]
]

# Транспонирование матрицы
transposed = list(zip(*numbers))

# Преобразование транспонированных столбцов в списки
transposed = [list(row) for row in transposed]

result = list(starmap(average, transposed))

print(result) # [7, 19, 12, 14, 59]

Две строки вместо создания кучи списков.

Давайте подводить итоги.

Заключение

Функция map() - это мощный инструмент для обработки данных в Python, который позволяет применять функции к элементам итерируемых объектов эффективно и компактно. Она особенно полезна, когда нужно обработать несколько объектов одновременно или когда важно оптимизировать использование памяти. В связке с другими инструментами, такими как lambda и starmap(), map() даёт широкие возможности для написания лаконичного и быстрого кода.


>>> Денис Ястребов

>>> Don Python

>>> Good coding ^_^

Report Page