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

Тур по Python Itertools

Примечание: это было первоначально опубликовано в Martinheinz.dev. Есть много отличных библиотек Python, но … Теги с Python, учебником, наукой данных.

Примечание: это было первоначально опубликовано в martinheinz.dev

Есть много замечательных библиотек Python, но большинство из них не приближаются к тому, что встроенный Itertools а также More-Itertools предоставлять. Эти две библиотеки действительно все кухонная мойка Когда дело доходит до обработки/итерации по некоторым данным в Python. На первый взгляд, однако, функции в этих библиотеках могут не казаться такими полезными, поэтому давайте сделаем небольшой тур (на мой взгляд) самых интересных, в том числе примеры, как максимально получить из них!

Компресс

Прежде чем мы добраться до функций из more_itertools Библиотека, давайте сначала посмотрим на несколько более неясных из встроенного Itertools Модуль – первый из них iTertools.compress. :

dates = [
    "2020-01-01",
    "2020-02-04",
    "2020-02-01",
    "2020-01-24",
    "2020-01-08",
    "2020-02-10",
    "2020-02-15",
    "2020-02-11",
]

counts = [1, 4, 3, 8, 0, 7, 9, 2]

from itertools import compress
bools = [n > 3 for n in counts]
print(list(compress(dates, bools)))  # Compress returns iterator!
#  ['2020-02-04', '2020-01-24', '2020-02-10', '2020-02-15']

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

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

Накапливаться

Как следует наименование – мы будем использовать эту функцию, чтобы накапливать результаты некоторой (двоичной) функции. Пример этого можно запустить максимум или факториал:

from itertools import accumulate
import operator

data = [3, 4, 1, 3, 5, 6, 9, 0, 1]

list(accumulate(data, max))  # running maximum
#  [3, 4, 4, 4, 5, 6, 9, 9, 9]

list(accumulate(range(1, 11), operator.mul))  # Factorial
#  [1, 2, 6, 24, 120, 720, 5040, 40320, 362880, 3628800]

Если вы не заботитесь о промежуточных результатах, вы можете использовать Functools.reduce (называется Fold на других языках), что поддерживает только окончательное значение и также является более эффективной памятью.

Цикл

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

# Cycling through players
from itertools import cycle

players = ["John", "Ben", "Martin", "Peter"]

next_player = cycle(players).__next__
player = next_player()
#  "John"

player = next_player()
#  "Ben"
#  ...

# Infinite Spinner
import time

for c in cycle('/-\|'):
    print(c, end = '\r')
    time.sleep(0.2)

Одна вещь, которую вам может потребоваться сделать при использовании цикл пропускает несколько элементов итеративных (другими словами – начиная с разных элементов). Вы можете сделать это с Itertools.islice , чтобы начать у третьего игрока из примера выше, вы могли бы сделать: islice (цикл (игроки), 2, нет) Отказ

Тройник

Финал один из Itertools модуль это тройник Эта функция создает несколько итераторов от одного, что позволяет нам помнить что случилось. Пример то есть ПОЛУЧЕНО Функция от Рецепты Itertools (а также fore_itertools ), который возвращает пары значений из ввода илетнее (текущее значение и предыдущее):

from itertools import tee

def pairwise(iterable):
    """
    s -> (s0, s1), (s1, s2), (s2, s3), ...
    """
    a, b = tee(iterable, 2)
    next(b, None)
    return zip(a, b)

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

more_itertools.

Теперь давайте посмотрим, что more_itertools Библиотека предлагает, так как есть много интересных функций, что вы, возможно, не слышали о.

делить

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

from more_itertools import divide
data = ["first", "second", "third", "fourth", "fifth", "sixth", "seventh"]

[list(l) for l in divide(3, data)]
#  [['first', 'second', 'third'], ['fourth', 'fifth'], ['sixth', 'seventh']]

Есть еще одна подобная функция в more_itertools называется распределить Однако это не поддерживает порядок. Если вам все равно, что вы должны использовать распределять Как это нужно меньше памяти.

разбиение

С помощью этой функции мы также будем разделить наше намерение, на этот раз, однако, используя предикат :

# Split based on age
from datetime import datetime, timedelta
from more_itertools import partition

dates = [ 
    datetime(2015, 1, 15),
    datetime(2020, 1, 16),
    datetime(2020, 1, 17),
    datetime(2019, 2, 1),
    datetime(2020, 2, 2),
    datetime(2018, 2, 4)
]

is_old = lambda x: datetime.now() - x < timedelta(days=30)
old, recent = partition(is_old, dates)
list(old)
#  [datetime.datetime(2015, 1, 15, 0, 0), datetime.datetime(2019, 2, 1, 0, 0), datetime.datetime(2018, 2, 4, 0, 0)]
list(recent)
#  [datetime.datetime(2020, 1, 16, 0, 0), datetime.datetime(2020, 1, 17, 0, 0), datetime.datetime(2020, 2, 2, 0, 0)]


# Split based on file extension
files = [
    "foo.jpg",
    "bar.exe",
    "baz.gif",
    "text.txt",
    "data.bin",
]

ALLOWED_EXTENSIONS = ('jpg','jpeg','gif','bmp','png')
is_allowed = lambda x: x.split(".")[1] in ALLOWED_EXTENSIONS

allowed, forbidden = partition(is_allowed, files)
list(allowed)
#  ['bar.exe', 'text.txt', 'data.bin']
list(forbidden)
#  ['foo.jpg', 'baz.gif']

В первом примере выше, мы разделяем список дат в последние и старые, используя простые лямбда функция. Для второго примера мы разбиваем файлы на основе их расширения, снова используя лямбда Функция, которая разбивает имя файла на имя и расширение и проверяет, находится ли расширение в списке разрешенных.

consecent_groups.

Если вам нужно найти прогоны последовательных чисел, даты, буквы, логии или любые другие упорядоченные объекты, тогда вы можете найти CONSECTION_GROUPS Удобный:

# Consecutive Groups of dates
import datetime
import more_itertools

dates = [ 
    datetime.datetime(2020, 1, 15),
    datetime.datetime(2020, 1, 16),
    datetime.datetime(2020, 1, 17),
    datetime.datetime(2020, 2, 1),
    datetime.datetime(2020, 2, 2),
    datetime.datetime(2020, 2, 4)
]

ordinal_dates = []
for d in dates:
    ordinal_dates.append(d.toordinal())

groups = [list(map(datetime.datetime.fromordinal, group)) for group in more_itertools.consecutive_groups(ordinal_dates)]

В этом примере у нас есть список дат, где некоторые из них подряд. Чтобы иметь возможность пройти эти даты CONSECTION_GROUPS Функция, мы сначала должны преобразовать их в порядковые номера. Затем используя понимание списка мы переиграем группы последовательных природовых дат, созданных CONSECTION_GROUPS и конвертировать их обратно в datetime.dateTime Использование карта и Figordinal Функции.

побочный эффект

Допустим, вам нужно вызвать побочный эффект при итерации над списком предметов. Этот побочный эффект может быть, например. Написание журналов, запись в файл или как в примере ниже подсчета количества событий, которые произошли:

import more_itertools
num_events = 0

def _increment_num_events(_):
    nonlocal num_events
    num_events += 1

# Iterator that will be consumed
event_iterator = more_itertools.side_effect(_increment_num_events, events)

more_itertools.consume(event_iterator)

print(num_events)

Мы объявляем простую функцию, которая будет увеличиваться счетчик каждый раз, когда она вызывается. Эта функция затем передана на Side_effect Наряду с неспецифическими считать, что называется События Отказ Позже, когда потребляется итератор события, он позвонит _Increment_num_events Для каждого предмета, давая нам конечные события.

крах

Это более мощная версия другого more_itertools Функция называется сплющить . коллапс Позволяет сгладить несколько уровней вложенности. Это также позволяет вам указать базовый тип, чтобы вы могли остановить уплотнение одним слоем списков/кортежей. Одним из употреблений для этой функции будет расточить Пандас Dataframe Отказ Вот несколько более общего назначения примеров:

import more_itertools
import os

# Get flat list of all files and directories
list(more_itertools.collapse(list(os.walk("/home/martin/Downloads"))))

# Get all nodes of tree into flat list
tree = [40, [25, [10, 3, 17], [32, 30, 38]], [78, 50, 93]]  # [Root, SUB_TREE_1, SUB_TREE_2, ..., SUB_TREE_n]
list(more_itertools.collapse(tree))

Первый генерирует список файлов и пути каталога, рухнувшие повторные панелы, возвращаемые Os.walk Отказ Во втором мы принимаем структуру данных деревьев в виде вложенных списков и разрушайте его, чтобы получить плоский список всех узлов указанного дерева.

Split_at.

Вернуться к разделению данных. Split_at Функция расщепляется с расщепленными в списках на основе предиката. Это работает как основные Сплит Для струн, но здесь у нас есть у нас, а не строка и функция предиката, а не разделитель:

import more_itertools

lines = [
    "erhgedrgh",
    "erhgedrghed",
    "esdrhesdresr",
    "ktguygkyuk",
    "-------------",
    "srdthsrdt",
    "waefawef",
    "ryjrtyfj",
    "-------------",
    "edthedt",
    "awefawe",
]

list(more_itertools.split_at(lines, lambda x: '-------------' in x))
#  [['erhgedrgh', 'erhgedrghed', 'esdrhesdresr', 'ktguygkyuk'], ['srdthsrdt', 'waefawef', 'ryjrtyfj'], ['edthedt', 'awefawe']]

Выше мы смоделируем текстовый файл с помощью списка строк. Это «Текстовый файл» Содержит строки с -------------- , который используется в качестве разделителя. Итак, это то, что мы используем в качестве нашего предиката для разделения этих линий в отдельные списки.

ведро

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

# Split based on Object Type
import more_itertools

class Cube:
    pass

class Circle:
    pass

class Triangle:
    pass

shapes = [Circle(), Cube(), Circle(), Circle(), Cube(), Triangle(), Triangle()]
s = more_itertools.bucket(shapes, key=lambda x: type(x))
# s -> 
list(s[Cube])
#  [<__main__.Cube object at 0x7f394a0633c8>, <__main__.Cube object at 0x7f394a063278>]
list(s[Circle])
# [<__main__.Circle object at 0x7f394a063160>, <__main__.Circle object at 0x7f394a063198>, <__main__.Circle object at 0x7f394a063320>]

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

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

Вероятно, самая интересная функция в этой библиотеке для всех научно-технических наук есть люди – map_recuce Отказ Я не собираюсь вписаться в деталь на том, как Уменьшение карты Работает так, как это не целью этой статьи, и есть много статей об этом уже. Что я собираюсь показать вам, как это использовать:

from more_itertools import map_reduce
data = 'This sentence has words of various lengths in it, both short ones and long ones'.split()

keyfunc = lambda x: len(x)
result = map_reduce(data, keyfunc)
# defaultdict(None, {
#   4: ['This', 'both', 'ones', 'long', 'ones'],
#   8: ['sentence'],
#   3: ['has', 'it,', 'and'],
#   5: ['words', 'short'],
#   2: ['of', 'in'],
#   7: ['various', 'lengths']})

valuefunc = lambda x: 1
result = map_reduce(data, keyfunc, valuefunc)
# defaultdict(None, {
#   4: [1, 1, 1, 1, 1],
#   8: [1],
#   3: [1, 1, 1],
#   5: [1, 1],
#   2: [1, 1],
#   7: [1, 1]})

reducefunc = sum
result = map_reduce(data, keyfunc, valuefunc, reducefunc)
# defaultdict(None, {
#   4: 5,
#   8: 1,
#   3: 3,
#   5: 2,
#   2: 2,
#   7: 2})

Это Уменьшение карты Реализация позволяет указывать 3 функции: ключ Функция (для классификации), ценность Функция (для преобразования) и, наконец, Уменьшить Функция (для уменьшения). Некоторые из этих функций могут быть опущены для создания промежуточных этапов Maprecuce процесс, как показано выше.

sort_together

Если вы работаете с электронными таблицами данных, шансы есть, что вам нужно отсортировать его по какой-то столбце. Это простая задача для sort_together Отказ Это позволяет нам указывать, с помощью которых столбец (ы) сортировки данных:

# Table
"""
      Name     |    Address    | Date of Birth |   Updated At 
----------------------------------------------------------------
John           |               |  1994-02-06   |   2020-01-06  
Ben            |               |  1985-04-01   |   2019-03-07  
Andy           |               |  2000-06-25   |   2020-01-08  
Mary           |               |  1998-03-14   |   2018-08-15  
"""

from more_itertools import sort_together
cols = [
    ("John", "Ben", "Andy", "Mary"),
    ("1994-02-06", "1985-04-01", "2000-06-25", "1998-03-14"),
    ("2020-01-06", "2019-03-07", "2020-01-08", "2018-08-15")
]

sort_together(cols, key_list=(1, 2))
#  [('Ben', 'John', 'Mary', 'Andy'), ('1985-04-01', '1994-02-06', '1998-03-14', '2000-06-25'), ('2019-03-07', '2020-01-06', '2018-08-15', '2020-01-08')]

Вход в функцию – это список iTerables (столбцы) и key_list который рассказывает sort_together Какая из поручений использовать для сортировки и с каким приоритетом. В случае приведенного выше примера с первым сортировкой “Стол” по Дата рождения а потом Обновлено в столбец.

ищущий

Мы все любим итераторы, но вы всегда должны быть осторожны с ними в Python как один из их Особенности Это то, что они потребляют поставляемые поставляемые. Им не нужно, хотя благодаря поиск :

from more_itertools import seekable

data = "This is example sentence for seeking back and forth".split()

it = seekable(data)
for word in it:
    ...

next(it)
# StopIteration
it.seek(3)
next(it)
# "sentence"

поиск Является функцией, которая включает в себя утечку в объекте, что позволяет ехать вперед и назад через итератор, даже после употребления некоторых элементов. В примере вы можете увидеть, что у нас есть Заставка Исключение после прохождения всего итератора, но мы можем обратиться за обратно и продолжать работать с ним.

filter_except.

Давайте посмотрим на следующий сценарий: вы получили смешанные данные, которые содержат как текст, так и цифры, и все это находится в строке. Вы, однако, хотите работать только с числами (Float/ints):

from more_itertools import filter_except

data = ['1.5', '6', 'not-important', '11', '1.23E-7', 'remove-me', '25', 'trash']
list(map(float, filter_except(float, data, TypeError, ValueError)))
#  [1.5, 6.0, 11.0, 1.23e-07, 25.0]

filter_except Фильтрует элементы ввода, передаваемые, проходящие элементы, указанные в предоставленной функции ( float ) и проверяя, бросает ли он ошибку ( TypeError, ValueError ) или нет, сохраняя только элементы, которые передавали проверку.

Unique_to_each.

Unique_to_each является одним из более неясных функций в more_itertools. библиотека. Он берет кучу iTerables и возвращает элементы из каждого из них, что не в других. Лучше посмотреть на примере:

from more_itertools import unique_to_each

# Graph (adjacency list)
graph = {'A': {'B', 'E'}, 'B': {'A', 'C'}, 'C': {'B'}, 'D': {'E'}, 'E': {'A', 'D'}}

unique_to_each({'B', 'E'}, {'A', 'C'}, {'B'}, {'E'}, {'A', 'D'})
# [[], ['C'], [], [], ['D']]
# If we discard B node, then C gets isolated and if we discard E node, then D gets isolated

Здесь мы определяем структуру данных графика, используя Список соседних (На самом деле Dict ). Затем мы проезжаем соседей каждого узла как набор Unique_to_each. . Какие его выходы представляют собой список узлов, которые будут быть изолированы, если необходимо удалить соответствующий узел.

numeric_range.

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

from more_itertools import numeric_range
import datetime
from decimal import Decimal

list(numeric_range(Decimal('1.7'), Decimal('3.5'), Decimal('0.3')))
#  [Decimal('1.7'), Decimal('2.0'), Decimal('2.3'), Decimal('2.6'), Decimal('2.9'), Decimal('3.2')]

start = datetime.datetime(2020, 2, 10)
stop = datetime.datetime(2020, 2, 15)
step = datetime.timedelta(days=2)
list(numeric_range(start, stop, step))
#  [datetime.datetime(2020, 2, 10, 0, 0), datetime.datetime(2020, 2, 12, 0, 0), datetime.datetime(2020, 2, 14, 0, 0)]

Что приятно о numeric_range Это так ведет себя так же, как основной диапазон . Вы можете указать Начните , Стоп и шаг Аргументы, как в примерах выше, где мы впервые используем десятичные случаи между 1.7 и 3.5 С этапом 0,3 а затем даты между 2020/2/10 и 2020/2/15 С шагом 2 дня.

make_decorator.

Последнее, но не менее важное, make_decorator Позволяет нам использовать другие IterTools в качестве декораторов и, следовательно, изменять выходы других функций, производящих итераторы:

from more_itertools import make_decorator
from more_itertools import map_except

mapper_except = make_decorator(map_except, result_index=1)

@mapper_except(float, ValueError, TypeError)
def read_file(f):
    ... # Read mix of text and numbers from file
    return ['1.5', '6', 'not-important', '11', '1.23E-7', 'remove-me', '25', 'trash']

list(read_file("file.txt"))
#  [1.5, 6.0, 11.0, 1.23e-07, 25.0]

Этот пример занимает map_except Функция и создает декоратор из этого. Этот декоратор будет потреблять результат украшенной функции в качестве второго аргумента ( RESLACT_INDEX = 1 ). В нашем случае украшенная функция – read_file , который имитирует данные чтения некоторых файлов и выводов списка строк, которые могут или не могут быть плавающими. Однако вывод сначала передается в декоратор, карт и фильтрует все нежелательные предметы, оставляя нас только с поплавками.

Заключение

Я надеюсь, что вы узнали что-то новое в этой статье, как Itertools и more_itertools Может сделать вашу жизнь намного проще, если вы часто обрабатываете много данных. Однако использование этих библиотек и функций требует эффективности некоторой практики. Итак, если вы думаете, что вы можете использовать некоторые из вещей, показанных в этой статье, то идите вперед и оформить заказ Рецепты Itertools Или просто заставьте себя использовать их максимально удобными для этого. 😉

Оригинал: “https://dev.to/martinheinz/tour-of-python-itertools-4122”