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

itertools – Функции итератора

Автор оригинала: Doug Hellmann.

Цель:

В модуль itertools входит набор функций для работы с наборы данных последовательности.

Функции, предоставляемые itertools , вдохновлены аналогичными функциями языков функционального программирования, таких как Clojure, Haskell, APL и SML. Они предназначены для быстрой работы и эффективного использования памяти, а также для объединения вместе для выражения более сложных алгоритмов, основанных на итерациях.

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

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

Слияние и разделение итераторов

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

itertools_chain.py

from itertools import *

for i in chain([1, 2, 3], ['a', 'b', 'c']):
    print(i, end' ')
print()

chain () позволяет легко обрабатывать несколько последовательностей без создания одного большого списка.

$ python3 itertools_chain.py

1 2 3 a b c

Если не все итерации, которые нужно объединить, заранее известны или их нужно оценивать лениво, для построения цепочки можно использовать chain.from_iterable () .

itertools_chain_from_iterable.py

from itertools import *


def make_iterables_to_chain():
    yield [1, 2, 3]
    yield ['a', 'b', 'c']


for i in chain.from_iterable(make_iterables_to_chain()):
    print(i, end' ')
print()
$ python3 itertools_chain_from_iterable.py

1 2 3 a b c

Встроенная функция zip () возвращает итератор, который объединяет элементы нескольких итераторов в кортежи.

itertools_zip.py

for i in zip([1, 2, 3], ['a', 'b', 'c']):
    print(i)

Как и в случае с другими функциями в этом модуле, возвращаемое значение – это повторяемый объект, который генерирует значения по одному.

$ python3 itertools_zip.py

(1, 'a')
(2, 'b')
(3, 'c')

zip () останавливается, когда исчерпывается первый итератор ввода. Чтобы обработать все входные данные, даже если итераторы выдают разное количество значений, используйте zip_longest () .

itertools_zip_longest.py

from itertools import *

r1  range(3)
r2  range(2)

print('zip stops early:')
print(list(zip(r1, r2)))

r1  range(3)
r2  range(2)

print('\nzip_longest processes all of the values:')
print(list(zip_longest(r1, r2)))

По умолчанию zip_longest () заменяет все отсутствующие значения None . Используйте аргумент fillvalue , чтобы использовать другое замещающее значение.

$ python3 itertools_zip_longest.py

zip stops early:
[(0, 0), (1, 1)]

zip_longest processes all of the values:
[(0, 0), (1, 1), (2, None)]

Функция islice () возвращает итератор, который возвращает выбранные элементы из итератора ввода по индексу.

itertools_islice.py

from itertools import *

print('Stop at 5:')
for i in islice(range(100), 5):
    print(i, end' ')
print('\n')

print('Start at 5, Stop at 10:')
for i in islice(range(100), 5, 10):
    print(i, end' ')
print('\n')

print('By tens to 100:')
for i in islice(range(100), 0, 100, 10):
    print(i, end' ')
print('\n')

islice () принимает те же аргументы, что и оператор среза для списков: start , stop и step . Аргументы start и step необязательны.

$ python3 itertools_islice.py

Stop at 5:
0 1 2 3 4

Start at 5, Stop at 10:
5 6 7 8 9

By tens to 100:
0 10 20 30 40 50 60 70 80 90

Функция tee () возвращает несколько независимых итераторов (по умолчанию 2) на основе одного исходного ввода.

itertools_tee.py

from itertools import *

r  islice(count(), 5)
i1, i2  tee(r)

print('i1:', list(i1))
print('i2:', list(i2))

tee () имеет семантику, аналогичную служебной программе Unix tee , которая повторяет значения, считываемые со своего ввода, и записывает их в именованный файл и стандартный вывод. Итераторы, возвращаемые функцией tee () , могут использоваться для передачи одного и того же набора данных в несколько алгоритмов для параллельной обработки.

$ python3 itertools_tee.py

i1: [0, 1, 2, 3, 4]
i2: [0, 1, 2, 3, 4]

Новые итераторы, созданные tee () , совместно используют свои входные данные, поэтому исходный итератор не следует использовать после создания новых.

itertools_tee_error.py

from itertools import *

r  islice(count(), 5)
i1, i2  tee(r)

print('r:', end' ')
for i in r:
    print(i, end' ')
    if i > 1:
        break
print()

print('i1:', list(i1))
print('i2:', list(i2))

Если значения потребляются из исходного ввода, новые итераторы не будут создавать эти значения:

$ python3 itertools_tee_error.py

r: 0 1 2
i1: [3, 4]
i2: [3, 4]

Преобразование входов

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

itertools_map.py

def times_two(x):
    return 2 * x


def multiply(x, y):
    return (x, y, x * y)


print('Doubles:')
for i in map(times_two, range(5)):
    print(i)

print('\nMultiples:')
r1  range(5)
r2  range(5, 10)
for i in map(multiply, r1, r2):
    print('{:d} * {:d} = {:d}'.format(*i))

print('\nStopping:')
r1  range(5)
r2  range(2)
for i in map(multiply, r1, r2):
    print(i)

В первом примере лямбда-функция умножает входные значения на 2. Во втором примере лямбда-функция умножает два аргумента, взятых из отдельных итераторов, и возвращает кортеж с исходными аргументами и вычисленным значением. Третий пример останавливается после создания двух кортежей, потому что второй диапазон исчерпан.

$ python3 itertools_map.py

Doubles:
0
2
4
6
8

Multiples:
0 * 5 = 0
1 * 6 = 6
2 * 7 = 14
3 * 8 = 24
4 * 9 = 36

Stopping:
(0, 0, 0)
(1, 1, 1)

Функция starmap () похожа на map () , но вместо создания кортежа из нескольких итераторов она разделяет элементы в одном итераторе в качестве аргументов сопоставления. функция с использованием синтаксиса * .

itertools_starmap.py

from itertools import *

values  [(0, 5), (1, 6), (2, 7), (3, 8), (4, 9)]

for i in starmap(lambda x, y: (x, y, x * y), values):
    print('{} * {} = {}'.format(*i))

Если функция сопоставления с map () называется f (i1, i2) , функция сопоставления, переданная в starmap () , называется f (* i) .

$ python3 itertools_starmap.py

0 * 5 = 0
1 * 6 = 6
2 * 7 = 14
3 * 8 = 24
4 * 9 = 36

Создание новых ценностей

Функция count () возвращает итератор, который производит последовательные целые числа на неопределенный срок. Первое число можно передать в качестве аргумента (по умолчанию – ноль). Нет аргумента верхней границы (см. Встроенный range () для большего контроля над набором результатов).

itertools_count.py

from itertools import *

for i in zip(count(1), ['a', 'b', 'c']):
    print(i)

Этот пример останавливается, так как аргумент списка использован.

$ python3 itertools_count.py

(1, 'a')
(2, 'b')
(3, 'c')

Аргументы start и step для count () могут быть любыми числовыми значениями, которые можно складывать вместе.

itertools_count_step.py

import fractions
from itertools import *

start  fractions.Fraction(1, 3)
step  fractions.Fraction(1, 3)

for i in zip(count(start, step), ['a', 'b', 'c']):
    print('{}: {}'.format(*i))

В этом примере начальная точка и шаги – это объекты Fraction из модуля Fraction .

$ python3 itertools_count_step.py

1/3: a
2/3: b
1: c

Функция cycle () возвращает итератор, который бесконечно повторяет содержимое переданных ей аргументов. Поскольку он должен запоминать все содержимое итератора ввода, он может потреблять довольно много памяти, если итератор длинный.

itertools_cycle.py

from itertools import *

for i in zip(range(7), cycle(['a', 'b', 'c'])):
    print(i)

В этом примере переменная счетчика используется для выхода из цикла после нескольких циклов.

$ python3 itertools_cycle.py

(0, 'a')
(1, 'b')
(2, 'c')
(3, 'a')
(4, 'b')
(5, 'c')
(6, 'a')

Функция repeat () возвращает итератор, который выдает одно и то же значение каждый раз, когда к нему обращаются.

itertools_repeat.py

from itertools import *

for i in repeat('over-and-over', 5):
    print(i)

Итератор, возвращаемый функцией repeat () , продолжает возвращать данные вечно, если не указан необязательный аргумент times , чтобы ограничить его.

$ python3 itertools_repeat.py

over-and-over
over-and-over
over-and-over
over-and-over
over-and-over

Полезно сочетать repeat () с zip () или map () , когда инвариантные значения необходимо включить со значениями из других итераторы.

itertools_repeat_zip.py

from itertools import *

for i, s in zip(count(), repeat('over-and-over', 5)):
    print(i, s)

В этом примере значение счетчика объединяется с константой, возвращаемой функцией repeat () .

$ python3 itertools_repeat_zip.py

0 over-and-over
1 over-and-over
2 over-and-over
3 over-and-over
4 over-and-over

В этом примере map () используется для умножения чисел в диапазоне от 0 до 4 на 2.

itertools_repeat_map.py

from itertools import *

for i in map(lambda x, y: (x, y, x * y), repeat(2), range(5)):
    print('{:d} * {:d} = {:d}'.format(*i))

Итератор repeat () не нуждается в явном ограничении, поскольку map () прекращает обработку, когда заканчивается любой из его входов, а range () возвращает только пять элементов.

$ python3 itertools_repeat_map.py

2 * 0 = 0
2 * 1 = 2
2 * 2 = 4
2 * 3 = 6
2 * 4 = 8

Фильтрация

Функция drop while () возвращает итератор, который создает элементы итератора ввода после того, как условие впервые становится ложным.

itertools_dropwhile.py

from itertools import *


def should_drop(x):
    print('Testing:', x)
    return x < 1


for i in dropwhile(should_drop, [-1, 0, 1, 2, -2]):
    print('Yielding:', i)

drop while () не фильтрует каждый элемент ввода; после того, как условие ложно в первый раз, возвращаются все оставшиеся элементы ввода.

$ python3 itertools_dropwhile.py

Testing: -1
Testing: 0
Testing: 1
Yielding: 1
Yielding: 2
Yielding: -2

Противоположностью drop while () является takewhile () . Он возвращает итератор, который возвращает элементы из итератора ввода, пока тестовая функция возвращает true.

itertools_takewhile.py

from itertools import *


def should_take(x):
    print('Testing:', x)
    return x < 2


for i in takewhile(should_take, [-1, 0, 1, 2, -2]):
    print('Yielding:', i)

Как только should_take () возвращает False , takewhile () прекращает обработку ввода.

$ python3 itertools_takewhile.py

Testing: -1
Yielding: -1
Testing: 0
Yielding: 0
Testing: 1
Yielding: 1
Testing: 2

Встроенная функция filter () возвращает итератор, который включает только элементы, для которых тестовая функция возвращает true.

itertools_filter.py

from itertools import *


def check_item(x):
    print('Testing:', x)
    return x < 1


for i in filter(check_item, [-1, 0, 1, 2, -2]):
    print('Yielding:', i)

filter () отличается от drop while () и takewhile () тем, что каждый элемент проверяется перед возвратом.

$ python3 itertools_filter.py

Testing: -1
Yielding: -1
Testing: 0
Yielding: 0
Testing: 1
Testing: 2
Testing: -2
Yielding: -2

filterfalse () возвращает итератор, который включает только те элементы, для которых тестовая функция возвращает false.

itertools_filterfalse.py

from itertools import *


def check_item(x):
    print('Testing:', x)
    return x < 1


for i in filterfalse(check_item, [-1, 0, 1, 2, -2]):
    print('Yielding:', i)

Тестовое выражение в check_item () такое же, поэтому результаты в этом примере с filterfalse () противоположны результатам из предыдущего примера.

$ python3 itertools_filterfalse.py

Testing: -1
Testing: 0
Testing: 1
Yielding: 1
Testing: 2
Yielding: 2
Testing: -2

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

itertools_compress.py

from itertools import *

every_third  cycle([False, False, True])
data  range(1, 10)

for i in compress(data, every_third):
    print(i, end' ')
print()

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

$ python3 itertools_compress.py

3 6 9

Группировка данных

Функция groupby () возвращает итератор, который создает наборы значений, упорядоченные по общему ключу. Этот пример иллюстрирует группировку связанных значений на основе атрибута.

itertools_groupby_seq.py

import functools
from itertools import *
import operator
import pprint


@functools.total_ordering
class Point:

    def __init__(self, x, y):
        self.x  x
        self.y  y

    def __repr__(self):
        return '({}, {})'.format(self.x, self.y)

    def __eq__(self, other):
        return (self.x, self.y)  (other.x, other.y)

    def __gt__(self, other):
        return (self.x, self.y) > (other.x, other.y)


# Create a dataset of Point instances
data  list(map(Point,
                cycle(islice(count(), 3)),
                islice(count(), 7)))
print('Data:')
pprint.pprint(data, width35)
print()

# Try to group the unsorted data based on X values
print('Grouped, unsorted:')
for k, g in groupby(data, operator.attrgetter('x')):
    print(k, list(g))
print()

# Sort the data
data.sort()
print('Sorted:')
pprint.pprint(data, width35)
print()

# Group the sorted data based on X values
print('Grouped, sorted:')
for k, g in groupby(data, operator.attrgetter('x')):
    print(k, list(g))
print()

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

$ python3 itertools_groupby_seq.py

Data:
[(0, 0),
 (1, 1),
 (2, 2),
 (0, 3),
 (1, 4),
 (2, 5),
 (0, 6)]

Grouped, unsorted:
0 [(0, 0)]
1 [(1, 1)]
2 [(2, 2)]
0 [(0, 3)]
1 [(1, 4)]
2 [(2, 5)]
0 [(0, 6)]

Sorted:
[(0, 0),
 (0, 3),
 (0, 6),
 (1, 1),
 (1, 4),
 (2, 2),
 (2, 5)]

Grouped, sorted:
0 [(0, 0), (0, 3), (0, 6)]
1 [(1, 1), (1, 4)]
2 [(2, 2), (2, 5)]

Объединение входов

Функция accumulate () обрабатывает итерацию ввода, передавая функции n-й и n + 1-й элемент и вырабатывая возвращаемое значение вместо любого из входных данных. Функция по умолчанию, используемая для объединения двух значений, складывает их, поэтому accumulate () можно использовать для получения кумулятивной суммы ряда числовых входных данных.

itertools_accumulate.py

from itertools import *

print(list(accumulate(range(5))))
print(list(accumulate('abcde')))

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

$ python3 itertools_accumulate.py

[0, 1, 3, 6, 10]
['a', 'ab', 'abc', 'abcd', 'abcde']

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

itertools_accumulate_custom.py

from itertools import *


def f(a, b):
    print(a, b)
    return b + a + b


print(list(accumulate('abcde', f)))

В этом примере строковые значения комбинируются таким образом, что получается серия (бессмысленных) палиндромов. На каждом этапе вызова f () он распечатывает входные значения, переданные ему с помощью accumulate () .

$ python3 itertools_accumulate_custom.py

a b
bab c
cbabc d
dcbabcd e
['a', 'bab', 'cbabc', 'dcbabcd', 'edcbabcde']

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

itertools_product.py

from itertools import *
import pprint

FACE_CARDS  ('J', 'Q', 'K', 'A')
SUITS  ('H', 'D', 'C', 'S')

DECK  list(
    product(
        chain(range(2, 11), FACE_CARDS),
        SUITS,
    )
)

for card in DECK:
    print('{:>2}{}'.format(*card), end' ')
    if card[1]  SUITS[-1]:
        print()

Значения, создаваемые product () , являются кортежами, члены которых берутся из каждого из итераций, переданных в качестве аргументов в порядке их передачи. Первый возвращенный кортеж включает первое значение из каждой итерации. Последняя итерация, переданная в product () , обрабатывается первой, затем предпоследней и т. Д. В результате возвращаемые значения располагаются по порядку на основе первой итерации, затем следующей итерации и т. Д.

В этом примере карты отсортированы по достоинству, а затем по масти.

$ python3 itertools_product.py

 2H  2D  2C  2S
 3H  3D  3C  3S
 4H  4D  4C  4S
 5H  5D  5C  5S
 6H  6D  6C  6S
 7H  7D  7C  7S
 8H  8D  8C  8S
 9H  9D  9C  9S
10H 10D 10C 10S
 JH  JD  JC  JS
 QH  QD  QC  QS
 KH  KD  KC  KS
 AH  AD  AC  AS

Чтобы изменить порядок карточек, измените порядок аргументов на product () .

itertools_product_ordering.py

from itertools import *
import pprint

FACE_CARDS  ('J', 'Q', 'K', 'A')
SUITS  ('H', 'D', 'C', 'S')

DECK  list(
    product(
        SUITS,
        chain(range(2, 11), FACE_CARDS),
    )
)

for card in DECK:
    print('{:>2}{}'.format(card[1], card[0]), end' ')
    if card[1]  FACE_CARDS[-1]:
        print()

Цикл печати в этом примере ищет карту туза вместо масти пик, а затем добавляет новую строку, чтобы разбить вывод.

$ python3 itertools_product_ordering.py

 2H  3H  4H  5H  6H  7H  8H  9H 10H  JH  QH  KH  AH
 2D  3D  4D  5D  6D  7D  8D  9D 10D  JD  QD  KD  AD
 2C  3C  4C  5C  6C  7C  8C  9C 10C  JC  QC  KC  AC
 2S  3S  4S  5S  6S  7S  8S  9S 10S  JS  QS  KS  AS

Чтобы вычислить произведение последовательности на себя, укажите, сколько раз ввод должен быть повторен.

itertools_product_repeat.py

from itertools import *


def show(iterable):
    for i, item in enumerate(iterable, 1):
        print(item, end' ')
        if (i % 3)  0:
            print()
    print()


print('Repeat 2:\n')
show(list(product(range(3), repeat2)))

print('Repeat 3:\n')
show(list(product(range(3), repeat3)))

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

$ python3 itertools_product_repeat.py

Repeat 2:

(0, 0) (0, 1) (0, 2)
(1, 0) (1, 1) (1, 2)
(2, 0) (2, 1) (2, 2)

Repeat 3:

(0, 0, 0) (0, 0, 1) (0, 0, 2)
(0, 1, 0) (0, 1, 1) (0, 1, 2)
(0, 2, 0) (0, 2, 1) (0, 2, 2)
(1, 0, 0) (1, 0, 1) (1, 0, 2)
(1, 1, 0) (1, 1, 1) (1, 1, 2)
(1, 2, 0) (1, 2, 1) (1, 2, 2)
(2, 0, 0) (2, 0, 1) (2, 0, 2)
(2, 1, 0) (2, 1, 1) (2, 1, 2)
(2, 2, 0) (2, 2, 1) (2, 2, 2)

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

itertools_permutations.py

from itertools import *


def show(iterable):
    first  None
    for i, item in enumerate(iterable, 1):
        if first  item[0]:
            if first is not None:
                print()
            first  item[0]
        print(''.join(item), end' ')
    print()


print('All permutations:\n')
show(permutations('abcd'))

print('\nPairs:\n')
show(permutations('abcd', r2))

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

$ python3 itertools_permutations.py

All permutations:

abcd abdc acbd acdb adbc adcb
bacd badc bcad bcda bdac bdca
cabd cadb cbad cbda cdab cdba
dabc dacb dbac dbca dcab dcba

Pairs:

ab ac ad
ba bc bd
ca cb cd
da db dc

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

itertools_combinations.py

from itertools import *


def show(iterable):
    first  None
    for i, item in enumerate(iterable, 1):
        if first  item[0]:
            if first is not None:
                print()
            first  item[0]
        print(''.join(item), end' ')
    print()


print('Unique pairs:\n')
show(combinations('abcd', r2))

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

$ python3 itertools_combinations.py

Unique pairs:

ab ac ad
bc bd
cd

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

itertools_combinations_with_replacement.py

from itertools import *


def show(iterable):
    first  None
    for i, item in enumerate(iterable, 1):
        if first  item[0]:
            if first is not None:
                print()
            first  item[0]
        print(''.join(item), end' ')
    print()


print('Unique pairs:\n')
show(combinations_with_replacement('abcd', r2))

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

$ python3 itertools_combinations_with_replacement.py

Unique pairs:

aa ab ac ad
bb bc bd
cc cd
dd

Смотрите также