【问题标题】:How to take the first N items from a generator or list? [duplicate]如何从生成器或列表中获取前 N 个项目? [复制]
【发布时间】:2021-09-13 00:32:19
【问题描述】:

我愿意

var top5 = array.Take(5);

如何用 Python 做到这一点?

【问题讨论】:

  • 对于列表和生成器都提出了这个问题,这令人困惑,这些问题应该是单独的问题

标签: linq python list generator


【解决方案1】:

对列表进行切片

top5 = array[:5]
  • 要对列表进行切片,有一个简单的语法:array[start:stop:step]
  • 您可以省略任何参数。这些都是有效的:array[start:]array[:stop]array[::step]

对生成器进行切片

import itertools
top5 = itertools.islice(my_list, 5) # grab the first five elements
  • 您不能直接在 Python 中对生成器进行切片。 itertools.islice() 将使用语法 itertools.islice(generator, start, stop, step) 将对象包装在新的切片生成器中

  • 请记住,切割发电机会部分耗尽它。如果你想保持整个生成器完好无损,不妨先把它变成一个元组或列表,比如:result = tuple(generator)

【讨论】:

  • 还要注意itertools.islice会返回一个生成器。
  • "如果你想保持整个生成器完好无损,也许先把它变成一个元组或列表" -> 在构建元组/列表的过程中不会完全耗尽生成器?
  • @lucid_dreamer 是的,但是你有一个新的数据结构(元组/列表),你可以随意迭代
  • 要在生成器耗尽之前创建副本,您也可以使用itertools.tee,例如:generator, another_copy = itertools.tee(generator)
【解决方案2】:

@Shaikovsky 的回答非常好(......自从我发布了这个答案后,我进行了大量编辑),但我想澄清几点。

[next(generator) for _ in range(n)]

这是最简单的方法,但如果生成器过早耗尽,则会抛出 StopIteration


另一方面,以下方法返回最多 n 个项目,这在许多情况下更可取:

列表: [x for _, x in zip(range(n), records)]

发电机: (x for _, x in zip(range(n), records))

【讨论】:

  • 能否请这几个人否决这个答案,请解释原因?
  • def take(num,iterable): return([elem for _ , elem in zip(range(num), iterable)])
  • 以上代码:循环遍历可以是生成器或列表的可迭代对象,并从可迭代对象中返回最多 n 个元素。如果 n 大于或等于 iterable 中存在的项目数,则返回 iterable 中的所有元素。
  • 这是最有效的。因为这不会处理完整列表。
  • [next(generator, None) for _ in range(n)] 如果你不介意None
【解决方案3】:

在我看来,将zip()xrange(n)(或Python3 中的range(n))结合起来也非常简洁,这在生成器上也很有效,而且对于一般的更改似乎更灵活。

# Option #1: taking the first n elements as a list
[x for _, x in zip(xrange(n), generator)]

# Option #2, using 'next()' and taking care for 'StopIteration'
[next(generator) for _ in xrange(n)]

# Option #3: taking the first n elements as a new generator
(x for _, x in zip(xrange(n), generator))

# Option #4: yielding them by simply preparing a function
# (but take care for 'StopIteration')
def top_n(n, generator):
    for _ in xrange(n): yield next(generator)

【讨论】:

    【解决方案4】:

    您是指 N 个项目,还是N 个最大 个项目?

    如果你想要第一个:

    top5 = sequence[:5]
    

    这也适用于最大的 N 个项目,假设您的序列按降序排序。 (您的 LINQ 示例似乎也假设了这一点。)

    如果你想要最大的,并且没有排序,最明显的解决方案是先排序:

    l = list(sequence)
    l.sort(reverse=True)
    top5 = l[:5]
    

    要获得更高性能的解决方案,请使用最小堆(感谢 Thijs):

    import heapq
    top5 = heapq.nlargest(5, sequence)
    

    【讨论】:

    • 不是先到先得吗?
    • import heapq; top5 = heapq.nlargest(5, iterable)
    • 谢谢,值得编辑!
    • 使用 sequence 而不是 iterable。一些 iterables 不支持索引。每个 sequence 都是一个 iterable,但有些 iterables 不是序列。
    • 注意 nlargest 接受任何可迭代的,不仅仅是序列。
    【解决方案5】:

    如何做到这一点的答案可以找到here

    >>> generator = (i for i in xrange(10))
    >>> list(next(generator) for _ in range(4))
    [0, 1, 2, 3]
    >>> list(next(generator) for _ in range(4))
    [4, 5, 6, 7]
    >>> list(next(generator) for _ in range(4))
    [8, 9]
    

    请注意,当只剩下 2 个时,最后一个调用请求接下来的 4 个。使用list() 而不是[] 是在next() 引发的StopIteration 异常上终止的理解。

    【讨论】:

    【解决方案6】:

    使用itertools,您将获得另一个生成器对象,因此在大多数情况下,您需要另一个步骤来获取前 N 个元素 (N)。至少有两种更简单的解决方案(在性能方面效率稍低,但非常方便)可以从 generator 获取准备好使用的元素:

    使用列表推导:

    first_N_element=[generator.next() for i in range(N)]
    

    否则:

    first_N_element=list(generator)[:N]
    

    其中N 是您要获取的元素数量(例如,前五个元素的 N=5)。

    【讨论】:

      【解决方案7】:

      这应该可以工作

      top5 = array[:5] 
      

      【讨论】:

      • @JoshWolff 我没有否决这个答案,但这可能是因为这种方法不适用于生成器,除非它们定义了__getitem__()。例如,尝试运行itertools.count()[:5](x for x in range(10))[:5],然后查看错误消息。然而,对于列表来说,答案是惯用的。
      【解决方案8】:
      import itertools
      
      top5 = itertools.islice(array, 5)
      

      【讨论】:

      • 当你用 None 代替 5 时,它还具有返回整个数组的好特性。
      • 如果你想每次取后面的五个你可以使用:iter(array) 而不是array。
      • 请注意,如果您的生成器耗尽,这不会产生错误,您将得到许多生成器留下的元素,小于您的请求大小。
      • 这是下面使用的方法:Itertools recipesdef take(n, iterable): return list(islice(iterable, n))
      猜你喜欢
      • 2020-01-13
      • 2017-12-06
      • 1970-01-01
      • 2017-07-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多