【问题标题】:Why is chaining iterables this complicated? Simplify this code为什么链式迭代如此复杂?简化此代码
【发布时间】:2015-02-11 04:17:27
【问题描述】:

我想链接多个可迭代对象,所有内容都使用惰性求值(速度至关重要),以执行以下操作:

  • 从一大行标准输入中读取多个整数
  • split() 那一行
  • 将生成的字符串转换为 int
  • 计算连续整数之间的差异
  • ...以及此处未显示的其他内容

真实的例子比较复杂,这里有一个简化的例子:

这是标准输入的示例行: 2 13 4 16 16 15 22 17 8 8 7 6

(出于调试目的,下面的instream 可能指向 sys.stdin,或打开的文件句柄)

您不能简单地链接生成器,因为map() 返回一个(惰性求值的)列表:

import itertools
gen1 = map(int, (map(str.split, instream))) # CAN'T CHAIN DIRECTLY

我找到的最简单的工作解决方案是这个,它肯定不能简化吗?

gen1 = map(int, itertools.chain.from_iterable(itertools.chain(map(str.split, instream))))

为什么我需要链接 itertools.chain.from_iterable(itertools.chain 只是为了处理来自 map(str.split, instream) 的结果 - 这有点违背目的? 手动定义生成器是否更快?

【问题讨论】:

    标签: python-3.x generator itertools chaining iterable


    【解决方案1】:

    显式(“手动”)生成器表达式应优先于使用mapfilter。它对大多数人来说更易读,也更灵活。

    如果我理解你的问题,这个生成器表达式可以满足你的需要:

    gen1 = ( int(x) for line in instream for x in line.split() )
    

    【讨论】:

    • 但我也说过我绝对需要惰性评估,形成line.split() 列表会减慢速度。以map(str.split, instream) 为起点。
    • @smci: map(str.split, instream) 形成与 line.split() 迭代时完全相同的列表; map 不能让 str.split 变得懒惰。
    • 好的。你能回答一下为什么我需要那个蹩脚的itertools.chain.from_iterable(itertools.chain(... 吗?
    • @smci: itertools.chain 有一个参数基本上什么都不做(它返回与其参数等效的东西)。简化为itertools.chain.from_iterable(...)
    【解决方案2】:

    您可以手动构建您的生成器:

    import string
    
    def gen1(stream):
        # presuming that stream is of type io.TextIOBase
    
    
        s = ""
        c = stream.read(1)  
        while len(c)>0:
    
            if (c not in string.digits):
                if len(s) > 0:
                    i = int(s)
                    yield i
                    s = ""
            else:
                s += c
    
            c = stream.read(1)
    
        if len(s) > 0:
            i = int(s)
            yield i 
    
    
    import io
    g = gen1(io.StringIO("12 45  6 7 88"))
    for x in g:    # dangerous if stream is unlimited
        print(x)
    

    这当然不是最漂亮的代码,但它可以满足您的需求。 说明:

    如果您的输入无限长,您必须分块(或按字符)阅读。 每当您遇到非数字(空白)时,您都将在该点之前读取的字符转换为整数并生成它。 您还必须考虑到达 EOF 时会发生什么。 由于我正在按字符阅读,我的实现可能不是很好。使用块可以显着加快速度。

    编辑为什么你的方法永远行不通:

    map(str.split, instream)
    

    根本不做你认为它做的事。 map 将给定函数 str.split 应用于作为第二个参数给出的迭代器的每个元素。在您的情况下,这是一个流,即一个文件对象,在 sys.stdin 的情况下特别是一个 io.TextIOBase 对象。这确实可以迭代。一行一行,这显然不是你想要的!实际上,您逐行遍历输入并将每一行拆分为单词。地图生成器迭代(许多)单词列表而不是单词列表。这就是为什么您必须将它们链接在一起以获得一个列表进行迭代。

    另外,itertools.chain.from_iterable(itertools.chain(map(...))) 中的 itertools.chain() 是多余的。 itertools.chain 将其参数(每个都是不可更改的对象)链接到一个迭代器中。你只给它一个参数,所以没有任何东西可以链接在一起,它基本上返回地图对象不变。 另一方面,itertools.chain.from_iterable() 接受一个参数,该参数应为迭代器的迭代器(例如列表列表)并将其展平为一个迭代器(列表)。

    EDIT2

    import io, itertools
    
    instream = io.StringIO("12 45 \n 66 7 88")
    gen1 = itertools.chain.from_iterable(map(str.split, instream))
    gen2 = map(int, gen1)
    list(gen2)
    

    返回

    [12, 45, 66, 7, 88]
    

    【讨论】:

    • 我发布的代码确实工作:gen1 = map(int, itertools.chain.from_iterable(itertools.chain(map(str.split, instream))))
    • 不,itertools.chain.from_iterable(itertools.chain( 不是多余的;没有它,代码就会中断。试试看,自己看看。
    • @smci:是的,确实如此,但它并没有按照您描述的那样做。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-01-27
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多