【问题标题】:Is there a way to 'pause' or partially consume a generator in Python, then resume consumption later where left off?有没有办法在 Python 中“暂停”或部分消耗生成器,然后在停止的地方恢复消耗?
【发布时间】:2019-05-14 07:45:21
【问题描述】:

有一个相关的问题here。我正在尝试在 HackerRank 上进行 this project Euler 挑战。它需要的是您能够导出字符串“abcdefghijklm”的第 n 个排列。有13个!排列。

我尝试了一个简单的解决方案,我使用了for num, stry in zip(range(1, math.factorial(13)), itertools.permutations("abcdefghijklm"):。这行得通,但它会超时。

真正好的是将每个值存储在dict 中,然后执行以下操作:

import itertools
import math

strt = "abcdefghijklm"

dic = {}

perms_gen = itertools.permutations(strt)
idxs_gen = range(1, math.factorial(13))

curr_idx = 0

test_list = [1, 2, 5, 10]

def get_elems(n):
  for num, stry in zip(idxs_gen, perms_gen):
    print(num) # debug
    str_stry = "".join(stry)
    dic[num] = str_stry
    if num == n:
      return str_stry

for x in test_list:
  if curr_idx < x:
    print(get_elems(x))
  else:
    print(dic[x])

这不起作用。我得到了这个输出:

1
abcdefghijklm
1
2
abcdefghijlkm
1
2
3
4
5
abcdefghikjml
1
2
3
4
5
6
7
8
9
10
abcdefghilmkj

在我写这个问题的时候,我显然找到了答案……待续。

【问题讨论】:

  • 哪个版本的 Python?在 Python 2 中,zip 返回一个列表。在 Python 3 中,zip 是一个类似于 itertools.izip 的迭代器,map 也类似于 itertools.imap。
  • @DanD。 Python 3。我认为这从代码中很明显?

标签: python python-3.x iteration generator


【解决方案1】:

当然你可以消耗迭代器it 的一部分,只需调用next(it) 消耗单个项目。或者,如果您需要一次使用多个,您可以编写一个函数来使用迭代器中的 n 项。在这两种情况下,您只需要注意迭代器还没有结束(StopIteration raise):

def consume(iterator, n):
  for i in range(n):
    try:
      yield next(iterator)
    except StopIteration:
      return

然后可以这样使用:

>>> r = iter(range(5))
>>> print(list(consume(r, 3)))
[0, 1, 2]
>>> print(list(consume(r, 3)))
[3, 4]

最后我不明白为什么你需要这个来解决这个特定的问题,并且正如建议的那样,有一个 python 函数itertools.permutations 已经为你迭代了所有排列。

【讨论】:

    【解决方案2】:

    暂停是生成器的内置功能。这是发电机的一半。但是,range 不是生成器。这是一种惰性序列类型。

    如果你想要一个对象,在它上面再次迭代将恢复你上次停止的地方,你想要一个迭代器在范围对象上:

    idsx_iter = iter(range(1, math.factorial(13)))
    

    但是,保存 zip 迭代器而不是两个底层迭代器会更简单。更好的是,使用enumerate

    indexed_permutations = enumerate(itertools.permutations(strt))
    

    不过,您的代码中还有很多没有意义的东西,例如 curr_idx,它永远保持为 0,或者您的 range 边界,它产生 13!-1 个索引13!索引,实际上,您应该使用更有效的算法。例如,通过将下一个元素设置为特定字符并使用它直接计算排列的每个元素来计算您跳过多少排列。

    【讨论】:

      【解决方案3】:

      标题中问题的答案是“是”,可以暂停再重新开始。怎么样?

      出乎意料(对我来说),显然zip() 重新启动了压缩生成器,尽管它们是先前定义的(也许有人可以告诉我为什么会发生这种情况?)。所以,我添加了main_gen = zip(idxs_gen, perms_gen) 并更改为for num, stry in zip(idxs_gen, perms_gen):for num, stry in main_gen:。然后我得到这个输出,假设字符串是正确的,这正是我想要的:

      1
      abcdefghijklm
      2
      abcdefghijkml
      3
      4
      5
      abcdefghijmkl
      6
      7
      8
      9
      10
      abcdefghiklmj
      

      修改后的代码如下:

      import itertools
      import math
      
      strt = "abcdefghijklm"
      
      dic = {}
      
      perms_gen = itertools.permutations(strt)
      idxs_gen = range(1, math.factorial(13))
      main_gen = zip(idxs_gen, perms_gen)
      
      curr_idx = 0
      
      test_list = [1, 2, 5, 10]
      
      def get_elems(n):
        for num, stry in main_gen:
          print(num)
          str_stry = "".join(stry)
          dic[num] = str_stry
          if num == n:
            return str_stry
      
      for x in test_list:
        if curr_idx < x:
          print(get_elems(x))
        else:
          print(dic[x])
      

      【讨论】:

      • zip 不会重启发电机。
      • @user2357112 你能解释一下该代码的输出吗?
      猜你喜欢
      • 1970-01-01
      • 2018-06-10
      • 1970-01-01
      • 1970-01-01
      • 2016-05-16
      • 1970-01-01
      • 2021-11-25
      • 2023-03-15
      • 1970-01-01
      相关资源
      最近更新 更多