【问题标题】:Iteration over list slices迭代列表切片
【发布时间】:2010-11-23 01:15:07
【问题描述】:

我想要一个算法来迭代列表切片。切片大小在函数外部设置,可以不同。

在我看来是这样的:

for list_of_x_items in fatherList:
    foo(list_of_x_items)

有没有办法正确定义 list_of_x_items 或使用 python 2.5 执行此操作的其他方式?


edit1:澄清“分区”和“滑动窗口”这两个术语听起来都适用于我的任务,但我不是专家。所以我会更深入地解释这个问题并添加到问题中:

fatherList 是我从文件中获取的多级 numpy.array。函数必须找到系列的平均值(用户提供系列的长度)对于平均我使用mean() 函数。现在进行问题扩展:

edit2:如何修改你提供的函数来存储额外的项目并在下一个fatherList被提供给函数时使用它们?

例如,如果列表长度为 10,块大小为 3,则存储列表的第 10 个成员并附加到下一个列表的开头。


相关:

【问题讨论】:

  • 您似乎在描述一种适用于原始列表子集的操作。 “切片”可以指各个连续范围,但您要查找的操作通常称为“分区”。您可能会发现这很有帮助:stackoverflow.com/questions/1198512/…
  • 我实际上是在考虑有时被称为序列上宽度为 n 的“滑动窗口”,但看看 Nadia 的回答,对这个问题有另一种解释,这可能更接近于“分区”。也许OP会澄清这一点。

标签: python loops iteration slice


【解决方案1】:

如果你想将一个列表分成多个切片,你可以使用这个技巧:

list_of_slices = zip(*(iter(the_list),) * slice_size)

例如

>>> zip(*(iter(range(10)),) * 3)
[(0, 1, 2), (3, 4, 5), (6, 7, 8)]

如果项目的数量不能被切片大小整除,并且你想用 None 填充列表,你可以这样做:

>>> map(None, *(iter(range(10)),) * 3)
[(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, None, None)]

这是一个肮脏的小把戏


好的,我会解释它是如何工作的。解释起来会很棘手,但我会尽力而为。

先介绍一点背景:

在 Python 中,您可以将列表乘以这样的数字:

[1, 2, 3] * 3 -> [1, 2, 3, 1, 2, 3, 1, 2, 3]
([1, 2, 3],) * 3 -> ([1, 2, 3], [1, 2, 3], [1, 2, 3])

iterator 对象可以像这样被消费一次:

>>> l=iter([1, 2, 3])
>>> l.next()
1
>>> l.next()
2
>>> l.next()
3

zip 函数返回一个元组列表,其中第 i 个元组包含来自每个参数序列或可迭代对象的第 i 个元素。例如:

zip([1, 2, 3], [20, 30, 40]) -> [(1, 20), (2, 30), (3, 40)]
zip(*[(1, 20), (2, 30), (3, 40)]) -> [[1, 2, 3], [20, 30, 40]]

zip 前面的 * 用于解压缩参数。您可以找到更多详细信息here。 所以

zip(*[(1, 20), (2, 30), (3, 40)])

实际上等价于

zip((1, 20), (2, 30), (3, 40))

但可以使用可变数量的参数

现在回到诀窍:

list_of_slices = zip(*(iter(the_list),) * slice_size)

iter(the_list) -> 将列表转换为迭代器

(iter(the_list),) * N -> 将生成一个对 the_list 迭代器的 N 引用。

zip(*(iter(the_list),) * N) -> 会将这些迭代器列表输入 zip。这反过来又会将它们分组为 N 个大小的元组。但由于所有 N 项实际上都是对同一个迭代器 iter(the_list) 的引用,因此结果将在原始迭代器上重复调用 next()

我希望这能解释清楚。我建议您使用更易于理解的解决方案。我只是想提及这个技巧,因为我喜欢它。

【讨论】:

  • 还有一件有趣的事情是,您正在默默地利用 * 解包运算符的低优先级,这允许像 ``f(LN)` 这样的有趣结构`,我发现没有任何记录。
  • @ThomasH: f(*args) 是 Python 中的扩展调用语法。您可以在星号 (*) 的右侧放置任何计算结果为可迭代的内容,例如 f(*[1]+g())
  • @J.F.Sebastian 是的,谢谢。令我印象深刻的只是运算符的优先级。您的示例解析为f(*([1]+g()))
  • 有点pythonic,我喜欢它
【解决方案2】:

如果您希望能够使用任何可迭代对象,您可以使用以下函数:

from itertools import chain, islice

def ichunked(seq, chunksize):
    """Yields items from an iterator in iterable chunks."""
    it = iter(seq)
    while True:
        yield chain([it.next()], islice(it, chunksize-1))

def chunked(seq, chunksize):
    """Yields items from an iterator in list chunks."""
    for chunk in ichunked(seq, chunksize):
        yield list(chunk)

【讨论】:

  • 非常漂亮。您实际上可以通过添加一个参数来选择每个块的类型来使其更智能:gist.github.com/1275417
  • 在 Python 3 中它是 next(it) 而不是 it.next()。对于那些想知道的人:islice 不会引发 StopIteration,这就是我们调用 next 的原因。
  • @Tim-Erwin 我在 python 3.6 中通过更改为next(it) 进行了尝试,但是我收到了警告DeprecationWarning: generator 'ichunked' raised StopIteration。我不明白这是在哪里提出的,因为它不明确。
  • @meowsqueak 在next() 提出。这在 Python 3.5 及更低版本中是预期的,在 3.6 中已弃用,在 3.7 中被破坏。我在答案中添加了与 3.7+ 兼容的版本。
  • @meowsqueak 其他人没有发现我的编辑有用并拒绝了它。您可以在下面找到它作为单独的答案。
【解决方案3】:

使用生成器:

big_list = [1,2,3,4,5,6,7,8,9]
slice_length = 3
def sliceIterator(lst, sliceLen):
    for i in range(len(lst) - sliceLen + 1):
        yield lst[i:i + sliceLen]

for slice in sliceIterator(big_list, slice_length):
    foo(slice)

sliceIterator 在序列lst 上实现宽度为sliceLen 的“滑动窗口”,即它产生重叠切片:[1,2,3], [2,3,4], [3,4 ,5], ... 不过,不确定这是否是 OP 的意图。

【讨论】:

    【解决方案4】:

    回答问题的最后一部分:

    问题更新:如何修改 您提供的存储功能 额外的物品并在 下一个父亲列表被馈送到 功能?

    如果你需要存储状态,那么你可以使用一个对象。

    class Chunker(object):
        """Split `iterable` on evenly sized chunks.
    
        Leftovers are remembered and yielded at the next call.
        """
        def __init__(self, chunksize):
            assert chunksize > 0
            self.chunksize = chunksize        
            self.chunk = []
    
        def __call__(self, iterable):
            """Yield items from `iterable` `self.chunksize` at the time."""
            assert len(self.chunk) < self.chunksize
            for item in iterable:
                self.chunk.append(item)
                if len(self.chunk) == self.chunksize:
                    # yield collected full chunk
                    yield self.chunk
                    self.chunk = [] 
    

    例子:

    chunker = Chunker(3)
    for s in "abcd", "efgh":
        for chunk in chunker(s):
            print ''.join(chunk)
    
    if chunker.chunk: # is there anything left?
        print ''.join(chunker.chunk)
    

    输出:

    abc
    def
    gh
    

    【讨论】:

    • 在 for 循环之后需要另一个 yield self.chunk 来说明不是完整块大小的块。否则,这将不适用于任意列表大小。
    • @aryeh: __call__ 可能会被多次调用,因此您不能只在 for 循环之后产生剩余部分。这就是课堂的重点。它回答了“存储额外的项目并在下一个fatherList被提供给函数时使用它们”部分问题,如答案顶部的明确说明。
    • 我的错误,我没有仔细阅读问题/示例。我以为只是为了分组。我个人不喜欢最后的额外检查,并会投票添加一个选项以 call 是否保存额外的项目或在最后刷新它们,因此您可能不需要复制主体在下面的 if 语句中循环。
    【解决方案5】:

    你的意思是这样的:

    def callonslices(size, fatherList, foo):
      for i in xrange(0, len(fatherList), size):
        foo(fatherList[i:i+size])
    

    如果这大致是您想要的功能,如果您愿意,可以在生成器中稍微修饰一下:

    def sliceup(size, fatherList):
      for i in xrange(0, len(fatherList), size):
        yield fatherList[i:i+size]
    

    然后:

    def callonslices(size, fatherList, foo):
      for sli in sliceup(size, fatherList):
        foo(sli)
    

    【讨论】:

    • 我想你会得到不规则大小的列表。没有确切地说出OP想要什么,但这可能更接近: for i in xrange(0, len(fatherList)-size, 1):
    • 更正:for i in xrange(len(fatherList) - size + 1):
    • “不规则尺寸”?他们都是 len size 除了可能是最后一个 - 并且不重叠。考虑一下当 len 为 6 且大小为 7 时,您的代码会发生什么情况,比如说... 5 个长度递减的切片...
    • 更好;-) 但是使用 len 6 和 size 8 你仍然有同样的问题。
    • 我一直在寻找可以开箱即用的好东西,因为它看起来完全是一般问题。我的笔记:不错的简单实现,但不能与迭代器一起使用... :( No len(list(fatherList)) 不是。
    【解决方案6】:

    我不确定,但您似乎想做所谓的移动平均线。 numpy 为此提供了便利(convolve 函数)。

    >>> x = numpy.array(范围(20)) >>> x 数组([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17、18、19]) >>> n = 2 # 移动平均窗口 >>> numpy.convolve(numpy.ones(n)/n, x)[n-1:-n+1] 数组([ 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, 8.5, 9.5, 10.5, 11.5, 12.5, 13.5, 14.5, 15.5, 16.5, 17.5, 18.5])

    好消息是它可以很好地适应不同的加权方案(只需将 numpy.ones(n) / n 更改为其他值)。

    您可以在此处找到完整的材料: http://www.scipy.org/Cookbook/SignalSmooth

    【讨论】:

      【解决方案7】:

      扩展@Ants Aasma 的答案:在 Python 3.7 中处理StopIteration 异常changed(根据PEP-479)。兼容的版本是:

      from itertools import chain, islice
      
      def ichunked(seq, chunksize):
          it = iter(seq)
          while True:
              try:
                  yield chain([next(it)], islice(it, chunksize - 1))
              except StopIteration:
                  return
      

      【讨论】:

        【解决方案8】:

        您的问题可以使用更多细节,但是如何:

        def iterate_over_slices(the_list, slice_size):
            for start in range(0, len(the_list)-slice_size):
                slice = the_list[start:start+slice_size]
                foo(slice)
        

        【讨论】:

          【解决方案9】:

          对于一个接近一个衬里(在itertools 导入之后),在 Nadia 的回答中处理非块可分大小而没有填充:

          >>> import itertools as itt
          >>> chunksize = 5
          >>> myseq = range(18)
          >>> cnt = itt.count()
          >>> print [ tuple(grp) for k,grp in itt.groupby(myseq, key=lambda x: cnt.next()//chunksize%2)]
          [(0, 1, 2, 3, 4), (5, 6, 7, 8, 9), (10, 11, 12, 13, 14), (15, 16, 17)]
          

          如果你愿意,你可以使用enumerate() 摆脱itertools.count() 的要求,但会更丑:

          [ [e[1] for e in grp] for k,grp in itt.groupby(enumerate(myseq), key=lambda x: x[0]//chunksize%2) ]
          

          (在本例中,enumerate() 是多余的,但显然并非所有序列都是这样的整齐范围)

          远没有其他答案那么简洁,但在紧要关头很有用,尤其是在已经导入 itertools 的情况下。

          【讨论】:

            【解决方案10】:

            将列表或迭代器切成给定大小的块的函数。如果最后一个块更小,也会正确处理这种情况:

            def slice_iterator(data, slice_len):
                it = iter(data)
                while True:
                    items = []
                    for index in range(slice_len):
                        try:
                            item = next(it)
                        except StopIteration:
                            if items == []:
                                return # we are done
                            else:
                                break # exits the "for" loop
                        items.append(item)
                    yield items
            

            使用示例:

            for slice in slice_iterator([1,2,3,4,5,6,7,8,9,10],3):
                print(slice)
            

            结果:

            [1, 2, 3]
            [4, 5, 6]
            [7, 8, 9]
            [10]
            

            【讨论】:

            • 执行其他一些解决方案在更多行中所做的事情,可能会慢一点,具体取决于用例。这可以使用 islice 来改进。
            猜你喜欢
            • 2015-02-04
            • 2015-01-24
            • 1970-01-01
            • 1970-01-01
            • 2020-05-08
            • 1970-01-01
            • 1970-01-01
            • 2019-07-23
            • 2021-10-26
            相关资源
            最近更新 更多