簡體   English   中英

在生成器內部返回生成器

[英]Returning a generator inside of a generator

我有一個整數列表和一個元組列表(表示間隔),我想編寫一個方法,為每個元組返回包含在間隔中的整數子列表,但我想用生成器來做。

對於以下輸入:

l = [1, 2, 3, 4, 5]
intervals = [(1, 2), (2, 4)]

子列表應該是: [1, 2][2, 3, 4]

我的嘗試:

def gen_intervals(l, intervals):
    for e in l:
        for i in intervals:
            if e > i[0] and e < i[1]:
                yield e

但是,這會給我一個元素列表,因為生成器一次產生一個元素。 我想要的是在那個區間產生一個元素的生成器

然后,我會這樣使用它:

for interval in gen_intervals(l, intervals):
    for e in interval:
        print(e)

重要的:

  • 該列表已排序,間隔也是如此(即使它們可能重疊)。 區間遵循區間的標准數學表示:[左端點,右端點],左端點 < 右端點。 對於任何兩個區間 u, v,它們不能是另一個的子集。 排序是指它們的正確端點按升序排序。

  • 我真的想先迭代元素,然后再迭代間隔,因為元素列表可能非常非常長,所以我只想迭代該列表一次。 元素列表的長度是>>區間列表的長度,但確切的長度是任意的。

您可以簡單地在yield線上使用生成器理解:

def gen_intervals(elements, intervals):
    for vmin, vmax in intervals:
        yield (elm for elm in elements if (vmin <= elm <= vmax))

這使:

l = [1, 2, 3, 4, 5]
intervals = [(2, 4), (1, 2)]

for interval in gen_intervals(l, intervals):
    for e in interval:
        print(e)
    print()
2
3
4

1
2

你可以這樣做。 每個間隔生成器都有一個緩沖區,當被要求提供項目時,它首先刷新其緩沖區,然后選擇下一個列表元素。 如果這個元素恰好是“它的”元素,則讓出它,否則將它放在相應區間的緩沖區中並嘗試下一個元素。

def items_by_iterval(lst, intervals):
    list_iter = iter(lst)
    buffers = [[] for _ in intervals]

    def interval_iter(n):
        while True:
            if buffers[n]:
                yield from buffers[n]
                buffers[n] = []

            try:
                k = next(list_iter)
            except StopIteration:
                return

            for m, (a, b) in enumerate(intervals):
                if a <= k <= b:
                    if m == n:
                        yield k
                    else:
                        buffers[m].append(k)
                    break

    return [interval_iter(n) for n, _ in enumerate(intervals)]


##

lst = [1, 9, 2, 8, 3, 7, 4, 6, 5, 1, 9, 2, 8, 3, 7, 4, 6, 5, ]
intervals = [(5, 7), (2, 4), (8, 10)]

for ii in items_by_iterval(lst, intervals):
    for k in ii:
        print(k, end=' ')
    print()

這打印:

7 6 5 7 6 5
2 3 4 2 3 4
9 8 9 8

代替任何for循環,您可以使用map為每個間隔的每個元素創建一個包含TrueFalse值的列表。 然后您可以使用itertools.compress創建一個迭代器 object ,該迭代器會產生相應區間內的元素:

import itertools

l = [1, 2, 3, 4, 5]
intervals = [(2, 4),]
for interval in intervals:
    # use range(i+1, j) to check if element is in interval
    mapped = map(lambda el: el in range(interval[0] + 1, interval[1]), l)
    # mapped = [False, False, True, False, False]
    res = itertools.compress(l, mapped)  # save this in case of more intervals

print(next(res))  # returns 3

基於@paime 的解決方案:

def iterate_interval(l, interval):
    for e in l:
        if e > interval[0]:
            if e < interval[1]:
                yield e
            else: break


def generate_windows(l, intervals):
    for interval in intervals:
        yield iterate_interval(l, interval)

這更有效,因為一旦元素大於區間的右端點,我們就不需要繼續迭代元素。

可能比你的更快,因為它會進行兩次快速二進制搜索來查找范圍,而不是將所有元素與區間界限進行比較。 此外,C 比 Python 快。

from bisect import bisect_left, bisect_right
from itertools import islice

def generate_windows(l, intervals):
    for left, right in intervals:
        yield islice(
            l,
            bisect_left(l, left),
            bisect_right(l, right)
        )

l = [1, 2, 3, 4, 5]
intervals = [(1, 2), (2, 4)]

for window in generate_windows(l, intervals):
    print(*window)

Output( 在線試用! ):

1 2
2 3 4

我還有其他一些可能更快的想法,通常我會編寫一個基准來比較各種解決方案,但為此我需要知道典型大小,包括每個間隔的列表元素的平均數量,而你沒有回答那個。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM