itertools --- توابعی برای ایجاد پیمایش‌گرها جهت حلقه‌زنی کارآمد


این ماژول تعدادی از بلوک‌های سازنده‌ی iterator را پیاده‌سازی می‌کند که از ساختارهایی در APL، Haskell و SML الهام گرفته‌اند. هر یک به شکلی مناسب برای Python بازطراحی شده است.

این ماژول مجموعه‌ای اصلی از ابزارهای سریع و کارآمد از نظر مصرف حافظه را استاندارد می‌کند که به‌تنهایی یا در ترکیب با یکدیگر مفید هستند. این ابزارها در کنار هم، یک «جبر پیمایش‌گر» (iterator algebra) را تشکیل می‌دهند که امکان ساخت ابزارهای تخصصی به‌صورت مختصر و کارآمد در پایتون خالص را فراهم می‌کند.

برای نمونه، SML یک ابزار جدول‌بندی (tabulation) ارائه می‌کند: tabulate(f) که دنباله‌ی f(0), f(1), ... را تولید می‌کند. همان اثر را می‌توان در پایتون با ترکیب map() و count() و تشکیل map(f, count()) به دست آورد.

پیمایش‌گرهای عمومی:

پیمایش‌گر

آرگومان‌ها

نتایج

مثال

accumulate()

p [,func]

p0, p0+p1, p0+p1+p2, ...

accumulate([1,2,3,4,5]) 1 3 6 10 15

batched()

p, n

(p0, p1, ..., p_n-1), ...

batched('ABCDEFG', n=3) ABC DEF G

chain()

p, q, ...

p0, p1, ... plast, q0, q1, ...

chain('ABC', 'DEF') A B C D E F

chain.from_iterable()

پیمایش‌پذیر

p0, p1, ... plast, q0, q1, ...

chain.from_iterable(['ABC', 'DEF']) A B C D E F

compress()

داده، انتخاب‌گرها

(d[0] if s[0]), (d[1] if s[1]), ...

compress('ABCDEF', [1,0,1,0,1,1]) A C E F

count()

[start[, step]]

start, start+step, start+2*step, ...

count(10) 10 11 12 13 14 ...

cycle()

p

p0, p1, ... plast, p0, p1, ...

cycle('ABCD') A B C D A B C D ...

dropwhile()

predicate, seq

seq[n]، seq[n+1]، شروع از زمانی که محمول برقرار نباشد

dropwhile(lambda x: x<5, [1,4,6,3,8]) 6 3 8

filterfalse()

predicate, seq

عناصری از seq که predicate(elem) برای آن‌ها ناموفق است

filterfalse(lambda x: x<5, [1,4,6,3,8]) 6 8

groupby()

iterable[, key]

زیرپیمایش‌گرهای گروه‌بندی‌شده بر اساس مقدار key(v)

groupby(['A','B','DEF'], len) (1, A B) (3, DEF)

islice()

seq, [start,] stop [, step]

عناصر از seq[start:stop:step]

islice('ABCDEFG', 2, None) C D E F G

pairwise()

پیمایش‌پذیر

(p[0], p[1]), (p[1], p[2])

pairwise('ABCDEFG') AB BC CD DE EF FG

repeat()

elem [,n]

elem، elem، elem، ... به‌طور بی‌پایان یا تا n بار

repeat(10, 3) 10 10 10

starmap()

تابع، دنباله

func(*seq[0]), func(*seq[1]), ...

starmap(pow, [(2,5), (3,2), (10,3)]) 32 9 1000

takewhile()

predicate, seq

seq[0]، seq[1]، تا زمانی که محمول (predicate) شکست نخورد

takewhile(lambda x: x<5, [1,4,6,3,8]) 1 4

tee()

it, n

it1, it2, ... itn یک پیمایش‌گر را به n تقسیم می‌کند

tee('ABC', 2) A B C, A B C

zip_longest()

p, q, ...

(p[0], q[0]), (p[1], q[1]), ...

zip_longest('ABCD', 'xy', fillvalue='-') Ax By C- D-

پیمایش‌گرهای ترکیبی:

پیمایش‌گر

آرگومان‌ها

نتایج

product()

p, q, ... [repeat=1]

حاصل‌ضرب دکارتی، معادل یک حلقه for تودرتو

permutations()

p[, r]

تاپل‌های به طول r، تمام ترتیب‌های ممکن، بدون عناصر تکراری

combinations()

p, r

تاپل‌های به طول r، به‌صورت مرتب‌شده، بدون عناصر تکراری

combinations_with_replacement()

p, r

تاپل‌هایی به طول r، به‌صورت مرتب‌شده، با عناصر تکراری

مثال‌ها

نتایج

product('ABCD', repeat=2)

AA AB AC AD BA BB BC BD CA CB CC CD DA DB DC DD

permutations('ABCD', 2)

AB AC AD BA BC BD CA CB CD DA DB DC

combinations('ABCD', 2)

AB AC AD BC BD CD

combinations_with_replacement('ABCD', 2)

AA AB AC AD BB BC BD CC CD DD

توابع Itertool

همه‌ی توابع زیر، پیمایش‌گرها را ایجاد می‌کنند و برمی‌گردانند. برخی از آن‌ها جریان‌هایی با طول بی‌نهایت فراهم می‌کنند، بنابراین باید فقط از طریق توابع یا حلقه‌هایی که جریان را کوتاه می‌کنند، به آن‌ها دسترسی پیدا کرد.

itertools.accumulate(iterable[, function, *, initial=None])

یک پیمایش‌گر می‌سازد که مجموع‌های انباشته یا نتایج انباشته از سایر توابع دودویی را بازمی‌گرداند.

function به‌طور پیش‌فرض جمع است. function باید دو آرگومان بپذیرد: یک مجموع انباشته و یک مقدار از iterable.

اگر یک مقدار اولیه ارائه شود، انباشت با آن مقدار آغاز می‌شود و خروجی یک عنصر بیشتر از پیمایش‌پذیر ورودی خواهد داشت.

تقریباً معادل با:

def accumulate(iterable, function=operator.add, *, initial=None):
    'Return running totals'
    # accumulate([1,2,3,4,5]) → 1 3 6 10 15
    # accumulate([1,2,3,4,5], initial=100) → 100 101 103 106 110 115
    # accumulate([1,2,3,4,5], operator.mul) → 1 2 6 24 120

    iterator = iter(iterable)
    total = initial
    if initial is None:
        try:
            total = next(iterator)
        except StopIteration:
            return

    yield total
    for element in iterator:
        total = function(total, element)
        yield total

برای محاسبه‌ی کمینه‌ی جاری، function را روی min() تنظیم کنید. برای بیشینه‌ی جاری، function را روی max() تنظیم کنید. یا برای حاصل‌ضرب جاری، function را روی operator.mul() تنظیم کنید. برای ساخت یک جدول استهلاک، سود را انباشته کنید و پرداخت‌ها را اعمال کنید:

>>> data = [3, 4, 6, 2, 1, 9, 0, 7, 5, 8]
>>> list(accumulate(data, max))              # running maximum
[3, 4, 6, 6, 6, 9, 9, 9, 9, 9]
>>> list(accumulate(data, operator.mul))     # running product
[3, 12, 72, 144, 144, 1296, 0, 0, 0, 0]

# Amortize a 5% loan of 1000 with 10 annual payments of 90
>>> update = lambda balance, payment: round(balance * 1.05) - payment
>>> list(accumulate(repeat(90, 10), update, initial=1_000))
[1000, 960, 918, 874, 828, 779, 728, 674, 618, 559, 497]

برای دیدن تابع مشابهی که تنها مقدار انباشته‌شده نهایی را برمی‌گرداند، functools.reduce() را ببینید.

اضافه شده در نسخه‌ی 3.2.

تغییر یافته در نسخه‌ی 3.3: پارامتر اختیاری function افزوده شد.

تغییر یافته در نسخه‌ی 3.8: پارامتر اختیاری initial افزوده شد.

itertools.batched(iterable, n, *, strict=False)

داده‌ها را از iterable به‌صورت تاپل‌هایی به طول n دسته‌بندی می‌کند. ممکن است آخرین دسته کوتاه‌تر از n باشد.

اگر strict درست باشد، در صورتی که آخرین دسته کوتاه‌تر از n باشد، یک ValueError پرتاب می‌شود.

Loops over the input iterable and accumulates data into tuples up to size n. The input is consumed lazily, just enough to fill a batch. The result is yielded as soon as the batch is full or when the input iterable is exhausted:

>>> flattened_data = ['roses', 'red', 'violets', 'blue', 'sugar', 'sweet']
>>> unflattened = list(batched(flattened_data, 2))
>>> unflattened
[('roses', 'red'), ('violets', 'blue'), ('sugar', 'sweet')]

تقریباً معادل با:

def batched(iterable, n, *, strict=False):
    # batched('ABCDEFG', 3) → ABC DEF G
    if n < 1:
        raise ValueError('n must be at least one')
    iterator = iter(iterable)
    while batch := tuple(islice(iterator, n)):
        if strict and len(batch) != n:
            raise ValueError('batched(): incomplete batch')
        yield batch

اضافه شده در نسخه‌ی 3.12.

تغییر یافته در نسخه‌ی 3.13: گزینه‌ی strict افزوده شد.

itertools.chain(*iterables)

Make an iterator that returns elements from the first iterable until it is exhausted, then proceeds to the next iterable, until all of the iterables are exhausted. This combines multiple data sources into a single iterator. Roughly equivalent to:

def chain(*iterables):
    # chain('ABC', 'DEF') → A B C D E F
    for iterable in iterables:
        yield from iterable
classmethod chain.from_iterable(iterable)

سازنده جایگزین برای chain(). ورودی‌های زنجیره‌شده را از یک آرگومان پیمایش‌پذیر واحد که به‌صورت تنبلانه ارزیابی می‌شود، دریافت می‌کند. تقریباً معادل است با:

def from_iterable(iterables):
    # chain.from_iterable(['ABC', 'DEF']) → A B C D E F
    for iterable in iterables:
        yield from iterable
itertools.combinations(iterable, r)

بازگرداندن زیردنباله‌هایی به طول r از عناصرِ iterable ورودی.

خروجی زیردنباله‌ای از product() است که تنها آیتم‌هایی را نگه می‌دارد که زیردنباله‌هایی از پیمایش‌پذیر هستند. طول خروجی توسط math.comb() داده می‌شود که n! / r! / (n - r)! را هنگامی که 0 r n یا صفر را هنگامی که r > n محاسبه می‌کند.

تاپل‌های ترکیبی بر اساس ترتیب پیمایش‌پذیر ورودی، به ترتیب لغت‌نامه‌ای تولید می‌شوند. اگر پیمایش‌پذیر ورودی مرتب باشد، تاپل‌های خروجی به ترتیب مرتب‌شده تولید می‌شوند.

عناصر بر اساس موقعیت‌شان یکتا در نظر گرفته می‌شوند، نه بر اساس مقدارشان. اگر عناصر ورودی یکتا باشند، هیچ مقدار تکراری در هر ترکیب وجود نخواهد داشت.

تقریباً معادل با:

def combinations(iterable, r):
    # combinations('ABCD', 2) → AB AC AD BC BD CD
    # combinations(range(4), 3) → 012 013 023 123

    pool = tuple(iterable)
    n = len(pool)
    if r > n:
        return
    indices = list(range(r))

    yield tuple(pool[i] for i in indices)
    while True:
        for i in reversed(range(r)):
            if indices[i] != i + n - r:
                break
        else:
            return
        indices[i] += 1
        for j in range(i+1, r):
            indices[j] = indices[j-1] + 1
        yield tuple(pool[i] for i in indices)
itertools.combinations_with_replacement(iterable, r)

زیردنباله‌هایی به طول r از عناصر iterable ورودی برمی‌گرداند و اجازه می‌دهد هر عنصر بیش از یک بار تکرار شود.

خروجی، زیردنباله‌ای از product() است که فقط آیتم‌هایی را نگه می‌دارد که زیردنباله‌هایی از پیمایش‌پذیر (با امکان تکرار عناصر) هستند. تعداد زیردنباله‌های برگردانده‌شده هنگامی که n > 0 باشد، (n + r - 1)! / r! / (n - 1)! است.

تاپل‌های ترکیبی بر اساس ترتیب پیمایش‌پذیر ورودی، به ترتیب واژگانی تولید می‌شوند. اگر پیمایش‌پذیر ورودی مرتب باشد، تاپل‌های خروجی به‌صورت مرتب تولید خواهند شد.

عناصر بر اساس جایگاهشان یکتا در نظر گرفته می‌شوند، نه بر اساس مقدارشان. اگر عناصر ورودی یکتا باشند، ترکیب‌های تولیدشده نیز یکتا خواهند بود.

تقریباً معادل با:

def combinations_with_replacement(iterable, r):
    # combinations_with_replacement('ABC', 2) → AA AB AC BB BC CC

    pool = tuple(iterable)
    n = len(pool)
    if not n and r:
        return
    indices = [0] * r

    yield tuple(pool[i] for i in indices)
    while True:
        for i in reversed(range(r)):
            if indices[i] != n - 1:
                break
        else:
            return
        indices[i:] = [indices[i] + 1] * (r - i)
        yield tuple(pool[i] for i in indices)

اضافه شده در نسخه‌ی 3.1.

itertools.compress(data, selectors)

Make an iterator that returns elements from data where the corresponding element in selectors is true. Stops when either the data or selectors iterables have been exhausted. Roughly equivalent to:

def compress(data, selectors):
    # compress('ABCDEF', [1,0,1,0,1,1]) → A C E F
    return (datum for datum, selector in zip(data, selectors) if selector)

اضافه شده در نسخه‌ی 3.1.

itertools.count(start=0, step=1)

پیمایش‌گری می‌سازد که مقادیر با فاصله‌های یکسان را از start برمی‌گرداند. می‌توان از آن همراه با map() برای تولید نقاط داده متوالی یا همراه با zip() برای افزودن شماره‌های ترتیب استفاده کرد. تقریباً معادل است با:

def count(start=0, step=1):
    # count(10) → 10 11 12 13 14 ...
    # count(2.5, 0.5) → 2.5 3.0 3.5 ...
    n = start
    while True:
        yield n
        n += step

هنگام شمارش با اعداد ممیز شناور، گاهی می‌توان با جایگزین کردن کد ضربی مانند: (start + step * i for i in count())، به دقت بهتری دست یافت.

تغییر یافته در نسخه‌ی 3.1: آرگومان step افزوده شد و آرگومان‌های غیر عدد صحیح مجاز شدند.

itertools.cycle(iterable)

Make an iterator returning elements from the iterable and saving a copy of each. When the iterable is exhausted, return elements from the saved copy. Repeats indefinitely. Roughly equivalent to:

def cycle(iterable):
    # cycle('ABCD') → A B C D A B C D A B C D ...

    saved = []
    for element in iterable:
        yield element
        saved.append(element)

    while saved:
        for element in saved:
            yield element

این ابزار تکرار (itertool) ممکن است به فضای ذخیره‌سازی کمکی قابل‌توجهی نیاز داشته باشد (بسته به طول پیمایش‌پذیر).

itertools.dropwhile(predicate, iterable)

یک پیمایش‌گر بسازید که تا زمانی که predicate درست است، عناصر را از iterable حذف می‌کند و پس از آن همه عناصر را برمی‌گرداند. تقریباً معادل:

def dropwhile(predicate, iterable):
    # dropwhile(lambda x: x<5, [1,4,6,3,8]) → 6 3 8

    iterator = iter(iterable)
    for x in iterator:
        if not predicate(x):
            yield x
            break

    for x in iterator:
        yield x

توجه داشته باشید که این ابزار پیمایش (itertool) تا پیش از آنکه شرط برای نخستین بار نادرست شود، هیچ خروجی تولید نمی‌کند، بنابراین ممکن است زمان راه‌اندازی طولانی داشته باشد.

itertools.filterfalse(predicate, iterable)

پیمایش‌گری می‌سازد که عناصر را از پیمایش‌پذیر پالایش می‌کند و فقط آن‌هایی را برمی‌گرداند که محمول برای آن‌ها مقدار نادرست برمی‌گرداند. اگر محمول None باشد، آیتم‌هایی که نادرست هستند را برمی‌گرداند. تقریباً معادل است با:

def filterfalse(predicate, iterable):
    # filterfalse(lambda x: x<5, [1,4,6,3,8]) → 6 8

    if predicate is None:
        predicate = bool

    for x in iterable:
        if not predicate(x):
            yield x
itertools.groupby(iterable, key=None)

یک پیمایش‌گر ایجاد کنید که کلیدها و گروه‌های متوالی را از iterable بازمی‌گرداند. key تابعی است که مقدار کلید هر المان را محاسبه می‌کند. اگر مشخص نشده باشد یا None باشد، key به‌طور پیش‌فرض یک تابع همانی است و المان را بدون تغییر بازمی‌گرداند. معمولاً، iterable باید از قبل بر اساس همان تابع key مرتب شده باشد.

عملکرد groupby() مشابه فیلتر uniq در یونیکس است. این تابع هر بار که مقدار تابع کلید تغییر کند، یک شکست یا گروه جدید ایجاد می‌کند (به همین دلیل معمولاً لازم است داده‌ها را با استفاده از همان تابع کلید مرتب کرده باشید). این رفتار با GROUP BY در SQL متفاوت است که عناصر مشترک را صرف‌نظر از ترتیب ورودی‌شان تجمیع می‌کند.

گروه برگردانده‌شده خودش یک پیمایش‌گر است که پیمایش‌پذیر زیربنایی را با groupby() به اشتراک می‌گذارد. از آن‌جا که منبع مشترک است، هنگامی که شیء groupby() پیشروی کند، گروه پیشین دیگر قابل مشاهده نیست. بنابراین، اگر بعداً به آن داده‌ها نیاز باشد، باید به‌صورت یک فهرست ذخیره شود:

groups = []
uniquekeys = []
data = sorted(data, key=keyfunc)
for k, g in groupby(data, keyfunc):
    groups.append(list(g))      # Store group iterator as a list
    uniquekeys.append(k)

groupby() تقریباً معادل است با:

def groupby(iterable, key=None):
    # [k for k, g in groupby('AAAABBBCCDAABBB')] → A B C D A B
    # [list(g) for k, g in groupby('AAAABBBCCD')] → AAAA BBB CC D

    keyfunc = (lambda x: x) if key is None else key
    iterator = iter(iterable)
    exhausted = False

    def _grouper(target_key):
        nonlocal curr_value, curr_key, exhausted
        yield curr_value
        for curr_value in iterator:
            curr_key = keyfunc(curr_value)
            if curr_key != target_key:
                return
            yield curr_value
        exhausted = True

    try:
        curr_value = next(iterator)
    except StopIteration:
        return
    curr_key = keyfunc(curr_value)

    while not exhausted:
        target_key = curr_key
        curr_group = _grouper(target_key)
        yield curr_key, curr_group
        if curr_key == target_key:
            for _ in curr_group:
                pass
itertools.islice(iterable, stop)
itertools.islice(iterable, start, stop[, step])

پیمایش‌گری می‌سازد که عناصر انتخاب‌شده از پیمایش‌پذیر را برمی‌گرداند. مانند اسلایس دنباله کار می‌کند، اما از مقادیر منفی برای start، stop یا step پشتیبانی نمی‌کند.

اگر start صفر یا None باشد، تکرار از صفر آغاز می‌شود. در غیر این صورت، عناصر پیمایش‌پذیر تا رسیدن به start نادیده گرفته می‌شوند.

If stop is None, iteration continues until the input is exhausted, if at all. Otherwise, it stops at the specified position.

اگر step برابر None باشد، گام به‌طور پیش‌فرض ۱ خواهد بود. المان‌ها به‌صورت متوالی برگردانده می‌شوند، مگر اینکه step بزرگ‌تر از ۱ تنظیم شده باشد که در این صورت آیتم‌ها رد می‌شوند.

تقریباً معادل با:

def islice(iterable, *args):
    # islice('ABCDEFG', 2) → A B
    # islice('ABCDEFG', 2, 4) → C D
    # islice('ABCDEFG', 2, None) → C D E F G
    # islice('ABCDEFG', 0, None, 2) → A C E G

    s = slice(*args)
    start = 0 if s.start is None else s.start
    stop = s.stop
    step = 1 if s.step is None else s.step
    if start < 0 or (stop is not None and stop < 0) or step <= 0:
        raise ValueError

    indices = count() if stop is None else range(max(start, stop))
    next_i = start
    for i, element in zip(indices, iterable):
        if i == next_i:
            yield element
            next_i += step

اگر ورودی یک پیمایش‌گر باشد، مصرف کامل islice، پیمایش‌گر ورودی را صرف‌نظر از مقدار step به اندازه‌ی max(start, stop) گام پیش می‌برد.

itertools.pairwise(iterable)

جفت‌های همپوشان متوالی گرفته‌شده از پیمایش‌پذیر ورودی را برمی‌گرداند.

تعداد تاپل‌های دوتایی (2-tuples) در پیمایش‌گر خروجی، یکی کمتر از تعداد ورودی‌ها خواهد بود. اگر پیمایش‌پذیر ورودی کمتر از دو مقدار داشته باشد، این پیمایش‌گر خالی خواهد بود.

تقریباً معادل با:

def pairwise(iterable):
    # pairwise('ABCDEFG') → AB BC CD DE EF FG

    iterator = iter(iterable)
    a = next(iterator, None)

    for b in iterator:
        yield a, b
        a = b

اضافه شده در نسخه‌ی 3.10.

itertools.permutations(iterable, r=None)

جایگشت‌های عناصر را به‌صورت متوالی و به طول r از iterable برمی‌گرداند.

اگر r مشخص نشده باشد یا None باشد، r به‌طور پیش‌فرض برابر با طول iterable خواهد بود و تمام جایگشت‌های ممکن با طول کامل تولید می‌شوند.

خروجی، زیردنباله‌ای از product() است که در آن آیتم‌های دارای عناصر تکراری حذف شده‌اند. طول خروجی از طریق math.perm() به دست می‌آید، که n! / (n - r)! را وقتی 0 r n یا صفر را وقتی r > n محاسبه می‌کند.

تاپل‌های جایگشت به ترتیب واژگانی، مطابق با ترتیب پیمایش‌پذیر ورودی تولید می‌شوند. اگر پیمایش‌پذیر ورودی مرتب باشد، تاپل‌های خروجی به ترتیب مرتب تولید خواهند شد.

عناصر بر اساس جایگاهشان یکتا در نظر گرفته می‌شوند، نه بر اساس مقدارشان. اگر عناصر ورودی یکتا باشند، هیچ مقدار تکراری در یک جایگشت وجود نخواهد داشت.

تقریباً معادل با:

def permutations(iterable, r=None):
    # permutations('ABCD', 2) → AB AC AD BA BC BD CA CB CD DA DB DC
    # permutations(range(3)) → 012 021 102 120 201 210

    pool = tuple(iterable)
    n = len(pool)
    r = n if r is None else r
    if r > n:
        return

    indices = list(range(n))
    cycles = list(range(n, n-r, -1))
    yield tuple(pool[i] for i in indices[:r])

    while n:
        for i in reversed(range(r)):
            cycles[i] -= 1
            if cycles[i] == 0:
                indices[i:] = indices[i+1:] + indices[i:i+1]
                cycles[i] = n - i
            else:
                j = cycles[i]
                indices[i], indices[-j] = indices[-j], indices[i]
                yield tuple(pool[i] for i in indices[:r])
                break
        else:
            return
itertools.product(*iterables, repeat=1)

ضرب دکارتی پیمایش‌پذیرهای ورودی.

تقریباً معادل حلقه‌های for تودرتو در یک عبارت تولیدگر است. برای مثال، product(A, B) همان ((x,y) for x in A for y in B) را برمی‌گرداند.

حلقه‌های تودرتو مانند مسافت‌سنج می‌چرخند و راست‌ترین عنصر در هر تکرار جلو می‌رود. این الگو یک ترتیب واژگانی ایجاد می‌کند، به‌طوری‌که اگر پیمایش‌پذیرهای ورودی مرتب باشند، تاپل‌های حاصل‌ضرب به‌صورت مرتب تولید می‌شوند.

برای محاسبه‌ی ضرب یک پیمایش‌پذیر در خودش، تعداد تکرارها را با آرگومان کلیدواژه‌ای اختیاری repeat مشخص کنید. برای مثال، product(A, repeat=4) به همان معنای product(A, A, A, A) است.

این تابع تقریباً معادل کد زیر است، با این تفاوت که پیاده‌سازی واقعی نتایج میانی را در حافظه ایجاد نمی‌کند:

def product(*iterables, repeat=1):
    # product('ABCD', 'xy') → Ax Ay Bx By Cx Cy Dx Dy
    # product(range(2), repeat=3) → 000 001 010 011 100 101 110 111

    if repeat < 0:
        raise ValueError('repeat argument cannot be negative')
    pools = [tuple(pool) for pool in iterables] * repeat

    result = [[]]
    for pool in pools:
        result = [x+[y] for x in result for y in pool]

    for prod in result:
        yield tuple(prod)

پیش از اجرای product()، این تابع پیمایش‌پذیرهای ورودی را به‌طور کامل مصرف می‌کند و مجموعه‌هایی از مقادیر را در حافظه نگه می‌دارد تا حاصل‌ضرب‌ها را تولید کند. بنابراین، فقط برای ورودی‌های متناهی به کار می‌آید.

itertools.repeat(object[, times])

یک پیمایش‌گر می‌سازد که object را بارها و بارها برمی‌گرداند. به‌طور نامحدود اجرا می‌شود، مگر اینکه آرگومان times مشخص شده باشد.

تقریباً معادل با:

def repeat(object, times=None):
    # repeat(10, 3) → 10 10 10
    if times is None:
        while True:
            yield object
    else:
        for i in range(times):
            yield object

یک کاربرد رایج repeat، فراهم کردن جریانی از مقادیر ثابت برای map یا zip است:

>>> list(map(pow, range(10), repeat(2)))
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
itertools.starmap(function, iterable)

پیمایش‌گری می‌سازد که function را با استفاده از آرگومان‌های به‌دست‌آمده از iterable محاسبه می‌کند. به جای map() زمانی استفاده می‌شود که پارامترهای آرگومان از پیش به‌صورت تاپل‌ها زیپ (pre-zipped) شده‌اند.

تفاوت میان map() و starmap() مشابه تمایز میان function(a,b) و function(*c) است. تقریباً معادل است با:

def starmap(function, iterable):
    # starmap(pow, [(2,5), (3,2), (10,3)]) → 32 9 1000
    for args in iterable:
        yield function(*args)
itertools.takewhile(predicate, iterable)

یک پیمایش‌گر می‌سازد که عناصر را از پیمایش‌پذیر تا زمانی که شرط درست باشد برمی‌گرداند. تقریباً معادل است با:

def takewhile(predicate, iterable):
    # takewhile(lambda x: x<5, [1,4,6,3,8]) → 1 4
    for x in iterable:
        if not predicate(x):
            break
        yield x

Note, the element that first fails the predicate condition is consumed from the input iterator and there is no way to access it. This could be an issue if an application wants to further consume the input iterator after takewhile has been run to exhaustion. To work around this problem, consider using more-itertools before_and_after() instead.

itertools.tee(iterable, n=2)

n پیمایش‌گرٔ مستقل از یک پیمایش‌پذیر واحد برمی‌گرداند.

تقریباً معادل با:

def tee(iterable, n=2):
    if n < 0:
        raise ValueError
    if n == 0:
        return ()
    iterator = _tee(iterable)
    result = [iterator]
    for _ in range(n - 1):
        result.append(_tee(iterator))
    return tuple(result)

class _tee:

    def __init__(self, iterable):
        it = iter(iterable)
        if isinstance(it, _tee):
            self.iterator = it.iterator
            self.link = it.link
        else:
            self.iterator = it
            self.link = [None, None]

    def __iter__(self):
        return self

    def __next__(self):
        link = self.link
        if link[1] is None:
            link[0] = next(self.iterator)
            link[1] = [None, None]
        value, self.link = link
        return value

هنگامی که iterable ورودی از پیش یک شیء پیمایش‌گر tee باشد، همه‌ی اعضای تاپل بازگشتی چنان ایجاد می‌شوند که گویی توسط فراخوانی بالادستی tee() تولید شده باشند. این «مرحله‌ی تخت‌سازی» اجازه می‌دهد که فراخوانی‌های تودرتوی tee() زنجیره‌ی داده‌ی زیربنایی یکسانی را به اشتراک بگذارند و به‌جای زنجیره‌ای از فراخوانی‌ها، یک مرحله‌ی به‌روزرسانی واحد داشته باشند.

ویژگی تخت‌سازی باعث می‌شود پیمایش‌گرهای tee به‌طور کارآمد قابل پیش‌نگری (peekable) باشند:

def lookahead(tee_iterator):
     "Return the next value without moving the input forward"
     [forked_iterator] = tee(tee_iterator, 1)
     return next(forked_iterator)
>>> iterator = iter('abcdef')
>>> [iterator] = tee(iterator, 1)   # Make the input peekable
>>> next(iterator)                  # Move the iterator forward
'a'
>>> lookahead(iterator)             # Check next value
'b'
>>> next(iterator)                  # Continue moving forward
'b'

پیمایش‌گرهای tee نخ‌ایمن نیستند. ممکن است هنگام استفاده‌ی همزمان از پیمایش‌گرهایی که توسط همان فراخوانی tee() برگردانده شده‌اند، RuntimeError پرتاب شود، حتی اگر پیمایش‌پذیر اصلی نخ‌ایمن باشد.

این ابزار تکرار (itertool) ممکن است به فضای ذخیره‌سازی کمکی قابل‌توجهی نیاز داشته باشد (بسته به این‌که چه مقدار داده موقت نیاز است ذخیره شود). به‌طور کلی، اگر یک پیمایش‌گر بیشتر یا تمام داده‌ها را پیش از شروع پیمایش‌گر دیگر مصرف کند، استفاده از list() به‌جای tee() سریع‌تر است.

itertools.zip_longest(*iterables, fillvalue=None)

یک پیمایش‌گر می‌سازد که المان‌ها را از هر یک از پیمایش‌پذیرها تجمیع می‌کند.

اگر پیمایش‌پذیرها طول‌های نابرابری داشته باشند، مقادیر جاافتاده با fillvalue پر می‌شوند. اگر مشخص نشده باشد، fillvalue به‌طور پیش‌فرض None خواهد بود.

Iteration continues until the longest iterable is exhausted.

تقریباً معادل با:

def zip_longest(*iterables, fillvalue=None):
    # zip_longest('ABCD', 'xy', fillvalue='-') → Ax By C- D-

    iterators = list(map(iter, iterables))
    num_active = len(iterators)
    if not num_active:
        return

    while True:
        values = []
        for i, iterator in enumerate(iterators):
            try:
                value = next(iterator)
            except StopIteration:
                num_active -= 1
                if not num_active:
                    return
                iterators[i] = repeat(fillvalue)
                value = fillvalue
            values.append(value)
        yield tuple(values)

اگر یکی از پیمایش‌پذیرها به‌طور بالقوه بی‌نهایت باشد، باید تابع zip_longest() در پوششی قرار گیرد که تعداد فراخوانی‌ها را محدود کند (برای مثال islice() یا takewhile()).

دستورپخت‌های Itertools

این بخش دستورالعمل‌هایی برای ایجاد یک مجموعه‌ابزار گسترش‌یافته با استفاده از itertools موجود به‌عنوان بلوک‌های سازنده ارائه می‌دهد.

هدف اصلی دستورالعمل‌های itertools، آموزشی است. این دستورالعمل‌ها روش‌های گوناگونی برای اندیشیدن درباره‌ی ابزارهای منفرد نشان می‌دهند — برای مثال، اینکه chain.from_iterable به مفهوم تخت‌سازی مربوط است. این دستورالعمل‌ها همچنین ایده‌هایی درباره‌ی شیوه‌های ترکیب ابزارها ارائه می‌دهند — برای مثال، اینکه چگونه starmap() و repeat() می‌توانند با هم کار کنند. این دستورالعمل‌ها همچنین الگوهایی برای استفاده از itertools با ماژول‌های operator و collections و همچنین با itertoolsهای توکار مانند map()، filter()، reversed() و enumerate() نشان می‌دهند.

هدف ثانویه‌ی دستورالعمل‌ها این است که به‌عنوان یک مرکز رشد عمل کنند. توابع accumulate()، compress() و pairwise() در itertools در ابتدا به‌صورت دستورالعمل معرفی شدند. در حال حاضر، دستورالعمل‌های sliding_window()، derangements() و sieve() در حال آزمایش هستند تا مشخص شود که آیا ارزش خود را اثبات می‌کنند یا خیر.

تقریباً تمام این دستورپخت‌ها و بسیاری، بسیاری دیگر را می‌توان از پروژه‌ی more-itertools موجود در Python Package Index نصب کرد:

python -m pip install more-itertools

بسیاری از دستورپخت‌ها همان کارایی بالای مجموعه‌ابزار زیرین را ارائه می‌دهند. کارایی برتر حافظه با پردازش المان‌ها به‌صورت یکی‌یکی حفظ می‌شود، نه با آوردن کل پیمایش‌پذیر به حافظه به‌صورت یکجا. حجم کد با پیوند دادن ابزارها به یکدیگر در یک سبک تابعی کوچک نگه داشته می‌شود. سرعت بالا با ترجیح بلوک‌های سازنده‌ی «بردارسازی‌شده» بر استفاده از حلقه‌های for و تولیدگرها حفظ می‌شود، زیرا این موارد سربار مفسر را تحمیل می‌کنند.

from itertools import (accumulate, batched, chain, combinations, compress,
     count, cycle, filterfalse, groupby, islice, permutations, product,
     repeat, starmap, tee, zip_longest)
from collections import Counter, deque
from contextlib import suppress
from functools import reduce
from heapq import heappush, heappushpop, heappush_max, heappushpop_max
from math import comb, isqrt, prod, sumprod
from operator import getitem, is_not, itemgetter, mul, neg, truediv


# ==== Basic one liners ====

def take(n, iterable):
    "اولین n آیتم از پیمایش‌پذیر را به‌عنوان یک فهرست برمی‌گرداند."
    return list(islice(iterable, n))

def prepend(value, iterable):
    "یک مقدار واحد را در ابتدای یک پیمایش‌پذیر قرار می‌دهد."
    # prepend(1, [2, 3, 4]) → 1 2 3 4
    return chain([value], iterable)

def repeatfunc(function, times=None, *args):
    "فراخوانی‌های یک تابع را با آرگومان‌های مشخص‌شده تکرار می‌کند."
    if times is None:
        return starmap(function, repeat(args))
    return starmap(function, repeat(args, times))

def flatten(list_of_lists):
    "یک سطح از تودرتویی را صاف می‌کند."
    return chain.from_iterable(list_of_lists)

def ncycles(iterable, n):
    "عناصر دنباله را n بار برمی‌گرداند."
    return chain.from_iterable(repeat(tuple(iterable), n))

def loops(n):
    "n بار حلقه می‌زند. مانند range(n) اما بدون ایجاد اعداد صحیح."
    # for _ in loops(100): ...
    return repeat(None, n)

def tail(n, iterable):
    "یک پیمایش‌گر روی آخرین n آیتم برمی‌گرداند."
    # tail(3, 'ABCDEFG') → E F G
    return iter(deque(iterable, maxlen=n))

def consume(iterator, n=None):
    "پیمایش‌گر را n مرحله جلو می‌برد. اگر n برابر None باشد، آن را به‌طور کامل مصرف می‌کند."
    # Use functions that consume iterators at C speed.
    if n is None:
        deque(iterator, maxlen=0)
    else:
        next(islice(iterator, n, n), None)

def nth(iterable, n, default=None):
    "آیتم n-اُم یا یک مقدار پیش‌فرض را برمی‌گرداند."
    return next(islice(iterable, n, None), default)

def quantify(iterable, predicate=bool):
    "با توجه به یک شرط که True یا False برمی‌گرداند، نتایج True را می‌شمارد."
    return sum(map(predicate, iterable))

def first_true(iterable, default=False, predicate=None):
    "اولین مقدار درست یا *default* را در صورت نبود مقدار درست برمی‌گرداند."
    # first_true([a, b, c], x) → a or b or c or x
    # first_true([a, b], x, f) → a if f(a) else b if f(b) else x
    return next(filter(predicate, iterable), default)

def all_equal(iterable, key=None):
    "اگر همه عناصر با یکدیگر برابر باشند، True برمی‌گرداند."
    # all_equal('4٤௪౪໔', key=int) → True
    return len(take(2, groupby(iterable, key))) <= 1


# ==== Data pipelines ====

def unique_justseen(iterable, key=None):
    "عناصر یکتا را با حفظ ترتیب تولید می‌کند. فقط عنصری را که همین حالا دیده شده است به خاطر می‌سپارد."
    # unique_justseen('AAAABBBCCDAABBB') → A B C D A B
    # unique_justseen('ABBcCAD', str.casefold) → A B c A D
    if key is None:
        return map(itemgetter(0), groupby(iterable))
    return map(next, map(itemgetter(1), groupby(iterable, key)))

def unique_everseen(iterable, key=None):
    "عناصر یکتا را با حفظ ترتیب تولید می‌کند. همه عناصری را که تاکنون دیده شده‌اند به خاطر می‌سپارد."
    # unique_everseen('AAAABBBCCDAABBB') → A B C D
    # unique_everseen('ABBcCAD', str.casefold) → A B c D
    seen = set()
    if key is None:
        for element in filterfalse(seen.__contains__, iterable):
            seen.add(element)
            yield element
    else:
        for element in iterable:
            k = key(element)
            if k not in seen:
                seen.add(k)
                yield element

def unique(iterable, key=None, reverse=False):
    "عناصر یکتا را به ترتیب مرتب‌شده تولید می‌کند. از ورودی‌های هش‌ناپذیر (unhashable) پشتیبانی می‌کند."
    # unique([[1, 2], [3, 4], [1, 2]]) → [1, 2] [3, 4]
    sequenced = sorted(iterable, key=key, reverse=reverse)
    return unique_justseen(sequenced, key=key)

def sliding_window(iterable, n):
    "داده‌ها را به تکه‌ها یا بلوک‌های ثابت‌طول همپوشان تبدیل می‌کند."
    # sliding_window('ABCDEFG', 3) → ABC BCD CDE DEF EFG
    iterator = iter(iterable)
    window = deque(islice(iterator, n - 1), maxlen=n)
    for x in iterator:
        window.append(x)
        yield tuple(window)

def grouper(iterable, n, *, incomplete='fill', fillvalue=None):
    "داده‌ها را به تکه‌ها یا بلوک‌های ثابت‌طول غیرهمپوشان تبدیل می‌کند."
    # grouper('ABCDEFG', 3, fillvalue='x')       → ABC DEF Gxx
    # grouper('ABCDEFG', 3, incomplete='strict') → ABC DEF ValueError
    # grouper('ABCDEFG', 3, incomplete='ignore') → ABC DEF
    iterators = [iter(iterable)] * n
    match incomplete:
        case 'fill':
            return zip_longest(*iterators, fillvalue=fillvalue)
        case 'strict':
            return zip(*iterators, strict=True)
        case 'ignore':
            return zip(*iterators)
        case _:
            raise ValueError('Expected fill, strict, or ignore')

def roundrobin(*iterables):
    "پیمایش‌پذیرهای ورودی را به‌صورت چرخه‌ای پیمایش می‌کند تا هر کدام تمام شوند."
    # roundrobin('ABC', 'D', 'EF') → A D E B F C
    # Algorithm credited to George Sakkis
    iterators = map(iter, iterables)
    for num_active in range(len(iterables), 0, -1):
        iterators = cycle(islice(iterators, num_active))
        yield from map(next, iterators)

def subslices(seq):
    "همه زیراسلایس‌های پیوسته و غیرخالی یک دنباله را برمی‌گرداند."
    # subslices('ABCD') → A AB ABC ABCD B BC BCD C CD D
    slices = starmap(slice, combinations(range(len(seq) + 1), 2))
    return map(getitem, repeat(seq), slices)

def derangements(iterable, r=None):
    "جایگشت‌هایی به طول r بدون نقاط ثابت تولید می‌کند."
    # derangements('ABCD') → BADC BCDA BDAC CADB CDAB CDBA DABC DCAB DCBA
    # Algorithm credited to Stefan Pochmann
    seq = tuple(iterable)
    pos = tuple(range(len(seq)))
    have_moved = map(map, repeat(is_not), repeat(pos), permutations(pos, r=r))
    valid_derangements = map(all, have_moved)
    return compress(permutations(seq, r=r), valid_derangements)

def iter_index(iterable, value, start=0, stop=None):
    "اندیس‌هایی را که یک مقدار در یک دنباله یا پیمایش‌پذیر در آن‌ها ظاهر می‌شود برمی‌گرداند."
    # iter_index('AABCADEAF', 'A') → 0 1 4 7
    seq_index = getattr(iterable, 'index', None)
    if seq_index is None:
        iterator = islice(iterable, start, stop)
        for i, element in enumerate(iterator, start):
            if element is value or element == value:
                yield i
    else:
        stop = len(iterable) if stop is None else stop
        i = start
        with suppress(ValueError):
            while True:
                yield (i := seq_index(value, i, stop))
                i += 1

def iter_except(function, exception, first=None):
    "یک رابط فراخوانی-تا-استثنا را به یک رابط پیمایش‌گر تبدیل می‌کند."
    # iter_except(d.popitem, KeyError) → non-blocking dictionary iterator
    with suppress(exception):
        if first is not None:
            yield first()
        while True:
            yield function()


# ==== Mathematical operations ====

def multinomial(*counts):
    "تعداد آرایش‌های متمایز یک چندمجموعه."
    # Counter('abracadabra').values() → 5 2 2 1 1
    # multinomial(5, 2, 2, 1, 1) → 83160
    return prod(map(comb, accumulate(counts), counts))

def powerset(iterable):
    "زیردنباله‌های پیمایش‌پذیر از کوتاه‌ترین تا بلندترین."
    # powerset([1,2,3]) → () (1,) (2,) (3,) (1,2) (1,3) (2,3) (1,2,3)
    s = list(iterable)
    return chain.from_iterable(combinations(s, r) for r in range(len(s)+1))

def sum_of_squares(iterable):
    "مربع‌های مقادیر ورودی را با هم جمع می‌کند."
    # sum_of_squares([10, 20, 30]) → 1400
    return sumprod(*tee(iterable))


# ==== Matrix operations ====

def reshape(matrix, columns):
    "شکل یک ماتریس دوبعدی را تغییر می‌دهد تا تعداد ستون‌های داده‌شده را داشته باشد."
    # reshape([(0, 1), (2, 3), (4, 5)], 3) →  (0, 1, 2) (3, 4, 5)
    return batched(chain.from_iterable(matrix), columns, strict=True)

def transpose(matrix):
    "ردیفها و ستون‌های یک ماتریس دوبعدی را جابه‌جا می‌کند."
    # transpose([(1, 2, 3), (11, 22, 33)]) → (1, 11) (2, 22) (3, 33)
    return zip(*matrix, strict=True)

def matmul(m1, m2):
    "دو ماتریس را ضرب می‌کند."
    # matmul([(7, 5), (3, 5)], [(2, 5), (7, 9)]) → (49, 80) (41, 60)
    n = len(m2[0])
    return batched(starmap(sumprod, product(m1, transpose(m2))), n)


# ==== Polynomial arithmetic ====

def convolve(signal, kernel):
    """کانولوشن خطی گسسته (discrete linear convolution) دو پیمایش‌پذیر.
    معادل ضرب چندجمله‌ای.

    کانولوشن‌ها از نظر ریاضی جابه‌جایی‌پذیرند؛ با این حال، ورودی‌ها به‌شکل
    متفاوتی ارزیابی می‌شوند.  سیگنال به‌صورت تنبل مصرف می‌شود و می‌تواند
    بی‌نهایت باشد. هسته پیش از آغاز محاسبات به‌طور کامل مصرف می‌شود.

    مقاله:  https://betterexplained.com/articles/intuitive-convolution/
    ویدیو:    https://www.youtube.com/watch?v=KuXjwB4LzSA
    """
    # convolve([1, -1, -20], [1, -3]) → 1 -4 -17 60
    # convolve(data, [0.25, 0.25, 0.25, 0.25]) → Moving average (blur)
    # convolve(data, [1/2, 0, -1/2]) → 1st derivative estimate
    # convolve(data, [1, -2, 1]) → 2nd derivative estimate
    kernel = tuple(kernel)[::-1]
    n = len(kernel)
    padded_signal = chain(repeat(0, n-1), signal, repeat(0, n-1))
    windowed_signal = sliding_window(padded_signal, n)
    return map(sumprod, repeat(kernel), windowed_signal)

def polynomial_from_roots(roots):
    """ضرایب یک چندجمله‌ای را از ریشه‌های آن محاسبه می‌کند.

       (x - 5) (x + 4) (x - 3)  بسط می‌یابد به:   x³ -4x² -17x + 60
    """
    # polynomial_from_roots([5, -4, 3]) → [1, -4, -17, 60]
    factors = zip(repeat(1), map(neg, roots))
    return list(reduce(convolve, factors, [1]))

def polynomial_eval(coefficients, x):
    """یک چندجمله‌ای را در یک مقدار مشخص ارزیابی می‌کند.

    با پایداری عددی بهتر از روش هورنر محاسبه می‌کند.
    """
    # Evaluate x³ -4x² -17x + 60 at x = 5
    # polynomial_eval([1, -4, -17, 60], x=5) → 0
    n = len(coefficients)
    if not n:
        return type(x)(0)
    powers = map(pow, repeat(x), reversed(range(n)))
    return sumprod(coefficients, powers)

def polynomial_derivative(coefficients):
    """مشتق اول یک چندجمله‌ای را محاسبه می‌کند.

       f(x)  =  x³ -4x² -17x + 60
       f'(x) = 3x² -8x  -17
    """
    # polynomial_derivative([1, -4, -17, 60]) → [3, -8, -17]
    n = len(coefficients)
    powers = reversed(range(1, n))
    return list(map(mul, coefficients, powers))


# ==== Number theory ====

def sieve(n):
    "اعداد اول کوچک‌تر از n."
    # sieve(30) → 2 3 5 7 11 13 17 19 23 29
    if n > 2:
        yield 2
    data = bytearray((0, 1)) * (n // 2)
    for p in iter_index(data, 1, start=3, stop=isqrt(n) + 1):
        data[p*p : n : p+p] = bytes(len(range(p*p, n, p+p)))
    yield from iter_index(data, 1, start=3)

def factor(n):
    "عوامل اول n."
    # factor(99) → 3 3 11
    # factor(1_000_000_000_000_007) → 47 59 360620266859
    # factor(1_000_000_000_000_403) → 1000000000000403
    for prime in sieve(isqrt(n) + 1):
        while not n % prime:
            yield prime
            n //= prime
            if n == 1:
                return
    if n > 1:
        yield n

def is_prime(n):
    "اگر n اول باشد، True برمی‌گرداند."
    # is_prime(1_000_000_000_000_403) → True
    return n > 1 and next(factor(n)) == n

def totient(n):
    "تعداد اعداد طبیعی تا n که با n متباین هستند."
    # https://mathworld.wolfram.com/TotientFunction.html
    # totient(12) → 4 because len([1, 5, 7, 11]) == 4
    for prime in set(factor(n)):
        n -= n // prime
    return n


# ==== Running statistics ====

def running_mean(iterable):
    "میانگین مقادیری که تاکنون دیده شده‌اند."
    # running_mean([37, 33, 38, 28]) → 37 35 36 34
    return map(truediv, accumulate(iterable), count(1))

def running_min(iterable):
    "کوچک‌ترین مقدار در میان مقادیری که تاکنون دیده شده‌اند."
    # running_min([37, 33, 38, 28]) → 37 33 33 28
    return accumulate(iterable, func=min)

def running_max(iterable):
    "بزرگ‌ترین مقدار در میان مقادیری که تاکنون دیده شده‌اند."
    # running_max([37, 33, 38, 28]) → 37 37 38 38
    return accumulate(iterable, func=max)

def running_median(iterable):
    "میانه مقادیری که تاکنون دیده شده‌اند."
    # running_median([37, 33, 38, 28]) → 37 35 37 35
    read = iter(iterable).__next__
    lo = []  # max-heap
    hi = []  # min-heap the same size as or one smaller than lo
    with suppress(StopIteration):
        while True:
            heappush_max(lo, heappushpop(hi, read()))
            yield lo[0]
            heappush(hi, heappushpop_max(lo, read()))
            yield (lo[0] + hi[0]) / 2

def running_statistics(iterable):
    "آمار تجمعی برای مقادیری که تاکنون دیده شده‌اند."
    # Generate tuples:  (size, minimum, median, maximum, mean)
    t0, t1, t2, t3 = tee(iterable, 4)
    return zip(
        count(1),
        running_min(t0),
        running_median(t1),
        running_max(t2),
        running_mean(t3),
    )