【问题标题】:Trouble understanding python generators and iterable arguments无法理解 python 生成器和可迭代参数
【发布时间】:2016-08-21 05:06:43
【问题描述】:

我应该编写一个生成器,给定一个可迭代参数列表,从第一个参数产生第一个元素,从第二个参数产生第一个元素,从第三个元素产生第一个元素,从第一个参数产生第二个元素等等。

所以

''.join([v for v in alternate('abcde','fg','hijk')]) == afhbgicjdke

我的函数适用于这样的字符串参数,但是当我尝试使用这样的给定测试用例时遇到问题

def hide(iterable):
for v in iterable:
    yield v

''.join([v for v in alternate(hide('abcde'),hide('fg'),hide('hijk'))])= afhbgicjdke

这是我的生成器:

def alternate(*args):
    for i in range(10):
        for arg in args:
            arg_num = 0
            for thing in arg:
                if arg_num == i:
                    yield thing
                arg_num+=1

我可以更改其中的某些内容以使其按描述工作,还是我的功能存在根本性问题?

编辑:作为作业的一部分,我不允许使用 itertools

【问题讨论】:

  • EDIT: as part of the assignment, I am not allowed to use itertools /me 仍然没有删除#homework 标签。 “作为运输任务的一部分,我不能使用轮子”。

标签: python iterator generator


【解决方案1】:

这样的东西可以正常工作:

def alternate(*iterables):
    iterators = [iter(iterable) for iterable in iterables]

    sentinel = object()

    keep_going = True
    while keep_going:
        keep_going = False
        for iterator in iterators:
            maybe_yield = next(iterator, sentinel)
            if maybe_yield != sentinel:
                keep_going = True
                yield maybe_yield

print ''.join(alternate('abcde','fg','hijk'))

诀窍在于,当生成器耗尽时,next 将返回哨兵值。只要有 1 个迭代器返回一个哨兵,那么我们就需要继续前进,直到它耗尽。如果哨兵不是从next返回的,那么这个值是好的,我们需要让出它。

请注意,如果可迭代对象的数量很大,则此实现是次优的(最好将可迭代对象存储在支持 O(1) 删除的数据结构中,并尽快删除可迭代对象检测到已用尽——collections.OrderedDict 可能用于此目的,但我会将其作为练习留给感兴趣的读者)。


如果我们想对标准库开放,itertools 也可以在这里提供帮助:

from itertools import izip_longest, chain
def alternate2(*iterables):
    sentinel = object()
    result = chain.from_iterable(izip_longest(*iterables, fillvalue=sentinel))
    return (item for item in result if item is not sentinel)

在这里,我返回了一个生成器表达式......这与编写生成器函数略有不同,但实际上并没有太大区别:-)。同样,如果有很多可迭代对象并且其中一个比其他对象长很多,这可能会稍微低效(考虑您有 100 个长度为 1 的可迭代对象和 1 个长度为 101 的可迭代对象的情况 - - 这将有效地运行 101 * 101 步,而您应该真正能够在大约 101 * 2 + 1 步内完成迭代。

【讨论】:

    【解决方案2】:

    您的代码中有几处可以改进的地方。导致您出现问题的原因是所有问题中最错误的 - 您实际上对每个参数进行了 几次 次迭代 - 并且基本上对每次传递中的中间值不做任何事情。

    当您为 i 的每个值迭代 for thing in arg 时,就会发生这种情况。

    虽然这在任何帐户中都是一种巨大的资源浪费,但它也不适用于迭代器(这是您使用 hide 函数获得的),因为它们在迭代其元素一次后会耗尽 - 也就是说与序列对比 - 可以迭代 - 并重新重复几个联系(如您用于测试的字符串)

    (另一个错误是将 10 硬编码为您曾经拥有的最长序列值 - 在 Python 中,您迭代生成器和序列 - 与它们的大小无关)

    无论如何,解决方法是确保您只对每个参数进行一次迭代 - 内置的 zip 可以做到这一点 - 或者对于您的用例,itertools.zip_longest(izip_longest 在 Python 2 中.x) 可以在单个 for 结构中从 args 中检索您想要的值:

    from itertools import izip_longest
    def alternate(*args):
        sentinel = object()
        for values in izip_longest(*args, fillvalue=sentinel):
             for value in values:
                  if value is not sentinel:
                       yield value
    

    【讨论】:

      【解决方案3】:

      如果您只想传递迭代器(这不适用于静态字符串),请使用以下代码:

      def alternate(*args):
          for i in range(10):
              for arg in args:
                  arg_num = i
                  for thing in arg:
                      if arg_num == i:
                          yield thing
                          break
                      else:
                          arg_num+=1
      

      这只是您的原始代码,稍作改动。 当您每次调用备用函数时都使用静态字符串时,将传入一个新字符串,您可以从 0 (arg_num = 0) 开始计数。

      但是当您通过调用 hide() 方法创建迭代器时,将为每个字符串创建一个迭代器实例,并且您应该跟踪您在迭代器中的位置,因此您必须将 arg_num = 0 更改为 arg_num = i而且你还需要添加break语句。

      【讨论】:

        猜你喜欢
        • 2016-07-09
        • 1970-01-01
        • 1970-01-01
        • 2014-06-08
        • 1970-01-01
        • 1970-01-01
        • 2017-09-03
        • 1970-01-01
        • 2015-02-10
        相关资源
        最近更新 更多