# тут у нас импорты
import itertools as it
import more_itertools as mit
import functools as ft
from random import randrange, choice # понадобятся для примеров
# этим будут выводиться результаты экспериментов
def p(iterable, upto=20):
"""Выводит на экран до @upto первых элементов итератора"""
print list(
it.islice(iterable, upto)
if upto else iterable)
# генератор тестовых данных
def generate(min_len=3, max_len=10):
"""Возвращает итератор бесконечному списку случайных строк
состоящих из символов [0-9a-f] и имеющих длину @min_len..@max_len"""
rng = range(min_len, max_len + 1)
while True:
yield ''.join(
choice('abcdef0123456789')
for _ in xrange(randrange(min_len, max_len + 1)))
# проверим, как это всё работает
p(generate(), 5)
['fe474f1e', '59aa5', '1b0f724a2', '75d95ad2d4', '475251a2']
# import functools as ft
Наши друзья:
# c partial всё просто
def repeat(s, n):
return s * (n + 1)
p(
map(
ft.partial(repeat, "*"), # тут мы применили repeat только к первому параметру (частично применили)
range(5)))
['*', '**', '***', '****', '*****']
# использование wraps КРАЙНЕ рекомендуется при написании декораторов
def printer(fn):
@ft.wraps(fn) # wraps копирует метаданные(имя, модуль, docstring) исходной функции в обёртку
def inner(*args, **kwargs):
res = fn(*args, **kwargs)
print "Result -> %s" % res
return res
return inner
@printer
def f(x,y):
"""
Очень важная функция!
"""
return x + y
f(10, 11)
help(f) # метаданные остались на месте!
Result -> 21 Help on function f in module __main__: f(*args, **kwargs) Очень важная функция!
@ft.total_ordering
class Color(object):
"""Цвет радуги"""
RED, ORANGE, YELLOW, GREEN, BLUE, NAVY_BLUE, PURPLE = _values = list(enumerate(
'Красный Оранжевый Желтый Зеленый Голубой Синий Фиолетовый'.split()
))
def __init__(self, value):
assert value in self._values
self._value_idx, self._value_name = value
def __repr__(self):
return self._value_name
# для полного набора операций сравнения нам достаточно определить только "==" и "<"
# остальные операторы на выведет total_ordering
def __eq__(self, other):
return isinstance(other, self.__class__) and (self._value_idx == other._value_idx)
def __lt__(self, other):
return isinstance(other, self.__class__) and (self._value_idx < other._value_idx)
# получаем экземпляры
red, orange, yellow, green, blue, navy, purple = map(
lambda x: Color(getattr(Color, x)),
['RED', 'ORANGE', 'YELLOW', 'GREEN', 'BLUE', 'NAVY_BLUE', 'PURPLE'])
# тестируем
if (
red < orange < green < purple > blue >= blue
and
yellow == Color(Color.YELLOW) # экземпляры разные, но суть одна
and
blue != navy
):
print "Всё хорошо! (пока)"
print sorted([blue, red, Color(Color.ORANGE), purple, green, yellow, red, navy])
Всё хорошо! (пока) [Красный, Красный, Оранжевый, Желтый, Зеленый, Голубой, Синий, Фиолетовый]
# import itertools as it
# всё как обычно, только лениво
p(
it.imap(
lambda (n, s): s * n,
it.ifilter(
lambda (n, s): n > 0,
it.izip(
[-5, 0, 3, 7, -2, -10, 0, 2, 1],
"abcdefghijklmnopqrstuvwxyz"))))
['ccc', 'ddddddd', 'hh', 'i']
starmap подставляет наборы (разной длины) параметров из одного источника, тогда как map подставляетв функцию с n аргументами по одному параметру из n источников
def fn(*args):
fmt, data = args[0], args[1:]
return '%r\t%% %r\t-> %s' % (fmt, data, fmt % data)
print "--- (i)map ---"
for s in it.imap(
fn,
['%08d', '%2.3f'], # два (в данном случае)
[100142, 3.14159] # "столбца" параметров
):
print s
print
print "--- starmap ---"
for s in it.starmap(
fn,
[ # один(!) список наборов параметров
('%08d', 100142),
('%d/%d', 3, 14),
('#%02X%02X%02X', 255, 127, 15), # наборы разной длины!
]
):
print s
--- (i)map --- '%08d' % (100142,) -> 00100142 '%2.3f' % (3.14159,) -> 3.142 --- starmap --- '%08d' % (100142,) -> 00100142 '%d/%d' % (3, 14) -> 3/14 '#%02X%02X%02X' % (255, 127, 15) -> #FF7F0F
p(
it.count(10, 5)
)
[10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95, 100, 105]
p(
it.chain(xrange(3), "asdasdsd")
)
[0, 1, 2, 'a', 's', 'd', 'a', 's', 'd', 's', 'd']
p(
it.cycle("abc")
)
['a', 'b', 'c', 'a', 'b', 'c', 'a', 'b', 'c', 'a', 'b', 'c', 'a', 'b', 'c', 'a', 'b', 'c', 'a', 'b']
p(
it.repeat(42)
)
[42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42]
p(
it.combinations("ADTF", 2)
)
[('A', 'D'), ('A', 'T'), ('A', 'F'), ('D', 'T'), ('D', 'F'), ('T', 'F')]
p(
it.product([1,2,3], "abc")
)
[(1, 'a'), (1, 'b'), (1, 'c'), (2, 'a'), (2, 'b'), (2, 'c'), (3, 'a'), (3, 'b'), (3, 'c')]
# разряженный массив
smile = set([(1, 1), (3, 1), (0, 3), (4, 3), (1, 4), (2, 4), (3, 4)])
def render(data, width, height):
return '\n'.join(
it.imap(''.join, (
([' ', '*'][(x, y) in data] for x in xrange(width))
for y in xrange(height))))
print render(smile, 5, 5)
# подсчет кол-ва точек в области
points_in_area = lambda data, (x0, y0), (x1, y1): sum(
(p in data)
for p in it.product(
xrange(x0, x1 + 1),
xrange(y0, y1 + 1)))
print
print "%s точек образуют \"рот\" смайлика" % points_in_area(smile, (0, 2), (4, 4))
* * * * *** 5 точек образуют "рот" смайлика
p(it.islice(xrange(100), 5, 15, 4))
[5, 9, 13]
gt_0 = lambda x: x < 0
p(it.takewhile(gt_0, xrange(-10, 10)))
p(it.dropwhile(gt_0, xrange(-10, 10)))
[-10, -9, -8, -7, -6, -5, -4, -3, -2, -1] [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
p(
it.compress(
it.count(),
it.cycle([1, 0, 1, 0, 0]))) # выборка по паттерну
[0, 2, 5, 7, 10, 12, 15, 17, 20, 22, 25, 27, 30, 32, 35, 37, 40, 42, 45, 47]
def frame(iterable, size=2):
"""Скользящий буфер размера @size"""
res = []
for i in xrange(size):
i, iterable = it.tee(iterable)
res.append(i)
iterable.next()
return it.izip(*res)
def sliding_avg(source, buf_len):
"""Сглаживание данных из @source усреднением по буферу размера @buf_len"""
avg = lambda x: float(sum(x)) / buf_len
return it.imap(avg, frame(source, buf_len))
p(
sliding_avg(
# бесконечный генератор случайных чисел в диапазоне [0,10)
it.imap(lambda _: randrange(0, 10), it.count()),
buf_len=20,
)
)
[4.65, 4.6, 4.65, 4.95, 5.25, 5.1, 5.25, 5.1, 5.15, 5.5, 5.35, 4.9, 4.7, 4.75, 4.65, 4.45, 4.1, 3.85, 3.85, 3.85]
data = sorted(
it.islice(
generate(min_len=1),
100),
key=len)
dict(it.imap(
lambda (x, y): (x, list(y)),
it.groupby(data, len)))
{1: ['9', 'b', '4', '7', 'e', '8', '3', 'd', 'd', '1', 'a', '2'], 2: ['bb', '0c', '3d', '75', '08', 'b1', '6c', '83', '77', '2a'], 3: ['73a', '210', 'c23', 'b36', '74d', 'ac3', 'ca8', '30c', 'de7', 'a22', '42d', 'bf9'], 4: ['c4c7', '3b3a', 'df50', 'c971', 'a22a', '4873', '196c', 'a547'], 5: ['f21fb', '8c833', '9dc39', 'd499d', '1cd4a', '0f09b', '3e622', '76247', '85295', '80418'], 6: ['18de45', '365293', 'ad2367', '832cc4', 'd680a4', '555a8e', '982b76', 'bed813', 'c7e023', '1bac6c', '49a2c0'], 7: ['b37b8b4', 'c6367ea', 'b77db1f', '0d6e5ff'], 8: ['1821efa7', 'acd32b48', '40591bd5', '423e4e74', 'cdcd5799', '33f514e1', '913d71cb', '3d89274d'], 9: ['5925a45bf', 'bd22a1201', 'cdb34486f', 'c71726d83', 'a68789b5a', '6503b1303', '7d8336679'], 10: ['735bbd62e3', '71d2519e4c', '2a4a802cd1', 'a57cf7c0af', 'd1dc411dfc', 'e6c4f0b943', '573451370c', '10a22ee3b0', '7d9cd79951', '9c8ba360d8', '9e6eed013b', 'c5239b89d6', 'e67e19fb54', '1df10f0654', '86c9449df5', '9dabc36169', 'db41cafb78', 'f663bd3c10']}
import more_itertools as mit
p(
mit.chunked(xrange(10), 3)
)
p(
mit.grouper(3, xrange(10), None)
)
[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]] [(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, None, None)]
print ''.join(
mit.collate(
sorted("ada78huh18jkashd"),
sorted("86daseqfr3657")
)
)
1356677888aaaadddefhhhjkqrssu
p(
mit.flatten(
[[1,2,3,4,5], [10, 20, 30], [400, 500]]))
[1, 2, 3, 4, 5, 10, 20, 30, 400, 500]
p(
it.imap(
lambda x: x[0],
mit.iterate(
lambda (a, b): (b, a + b),
(1, 1))))
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765]
p(
mit.repeatfunc(
randrange, None, 1, 7))
[6, 2, 1, 2, 2, 3, 1, 4, 5, 2, 6, 1, 5, 4, 6, 4, 3, 1, 3, 6]
def pairwise(source):
peekable_source = mit.peekable(source)
for i in peekable_source:
yield (i, peekable_source.peek())
p(
pairwise(xrange(10))
)
[(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (6, 7), (7, 8), (8, 9)]
print mit.first("abc", None)
print mit.first("", None)
a None
p(
mit.take(3, it.count(1000)))
[1000, 1001, 1002]
print mit.nth("abcdefg", 5)
f
data = (x for x in xrange(10)) # неперезапускаемый итератор
mit.consume(data, 5) # слили 5 элементов в эфир
p(data)
[5, 6, 7, 8, 9]
p(
mit.take(10, mit.padnone("abc"))
)
['a', 'b', 'c', None, None, None, None, None, None, None]
print mit.ilen(xrange(10))
10
# проход по менеджеру контекста, поддерживающему протокол итерации,
# с последующим закрытием (менеджера)
# код закомментирован, т.к. зацикливается по причине бесконечности исходного файла :)
# p(
# it.imap(
# int,
# mit.with_iter(
# open('/dev/random')))
chunked в виде комбинации готовых функций (в исходник я не смотрел специально)
chunked = lambda iterable, size: (
it.takewhile(bool, it.imap(list, mit.repeatfunc(it.islice, None, iter(iterable), size)))
)
print list(chunked(xrange(10), 3))
[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]]
Теперь добавим парочку вспомогательных функций для "проталкивания" данных через цепочку функций
do = lambda x, *tups: reduce(lambda x, tup: tup[0](x, *tup[1:]), tups, x)
do_last = lambda x, *tups: reduce(lambda x, tup: tup[0](*(tup[1:] + (x,))), tups, x)
# пример использования
print do(
" hello, world ? ",
(str.strip,),
(str.split,),
(' '.join,),
(str.capitalize,),
(str.replace,'?','!')
)
Hello, world !
# и ещё один
print do(
generate(), # случайные строки
(it.islice, 100), # в кол-ве 100 штук
(lambda x: sorted(x, key=len),), # сортируем по длине (тут, увы, требуется keyword-аргумент, поэтому lambda)
(it.groupby, len), # группируем по длине же
(dict,), # превращаем в словарь
(dict.keys,), # возвращаем его ключи
(set,) # в виде множества
), "<- множество длин случайных строк"
set([3, 4, 5, 6, 7, 8, 9, 10]) <- множество длин случайных строк
Теперь нашу версию chunked можно переписать так:
chunked = lambda iterable, size: do_last(
iterable, # исходные данные
(iter,), # принудительно превращаем в неперезапускаемый итератор
(lambda x: mit.repeatfunc(it.islice, None, x, 3),), # раз за разом применяем islice,
(it.imap, list), # форсируя ленивые слайсы превращением их в списки,
(it.takewhile, bool), # продолжаем обработку, пока не попадётся первый пустой список
)
print list(chunked(xrange(10), 3))
[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]]