【问题标题】:Consecutive slices of iterable可迭代的连续切片
【发布时间】:2017-10-22 00:55:57
【问题描述】:

假设我有一个迭代器

numbers = iter(range(100))

我想计算连续的平均值并将它们与元素一起存储在iterable

0., 0.5, ..., 49., 49.5

这可以通过将 iterable 转换为 list/tuple 并计算其 slices 来完成

from statistics import mean

# in cases with large or potentially infinite amounts of data
# this conversion will fail
numbers_list = list(numbers)
numbers_slices = (numbers_list[:end + 1] for end in range(len(numbers_list)))
mean_values = map(mean, numbers_slices)

(关于mean函数的更多信息docs

所以我的问题更笼统:有没有办法使用标准库获得连续的可迭代切片而不用 list/tuple 包装?


我们可以像这样写效用函数

def get_slices(iterable):
    elements = []
    for element in iterable:
        elements.append(element)
        yield elements

然后

numbers_slices = get_slices(numbers)
mean_values = map(mean, numbers_slices)

但它看起来也很糟糕


P. S.:我知道计算连续的平均值会更好,例如

def get_mean_values(numbers):
    numbers_sum = 0
    for numbers_count, number in enumerate(numbers, start=1):
        numbers_sum += number
        yield numbers_sum / numbers_count

但这不是我要说的。

【问题讨论】:

  • 所以你没有像你最初所说的 range 对象?您实际上可以直接索引 py 3 range 对象,而无需转换为 list
  • @Chris_Rands:就我而言,我有一台发电机,已修复
  • 这似乎是使用list 是最好的主意的情况,因为迭代器已被消耗,因此您必须为每个切片构建一个新的迭代器。 itertools.tee 可以做到这一点,但速度可能很慢
  • 我看到的唯一另一种方法是,如果您的生成器恰好有 __reversed__() 方法,那么您可以反向制作切片,但我想它没有

标签: python python-3.x slice iterable


【解决方案1】:

似乎没有获取iterable的连续切片的标准方法(iterator/list/tuple/etc)

我发现更好的方法是使用原始问题中的一些修改后的实用函数

def consecutive_slices(iterable):
    elements = []
    for element in iterable:
        elements.append(element)
        yield list(elements)

修改

  • 添加了elements的复制(顺便说一下doing that有很多方法),因为以前的版本在list中包装的情况下

    >>> numbers_slices = list(get_slices(numbers))
    

    将给我们listN 重复elements 以及其中的所有数字(N 在示例中等于 100):

    >>> numbers_slices == [list(range(100))] * 100
    True
    

功能方法

在写了一点之后,我意识到这也可以使用itertools module 来完成

from itertools import (accumulate,
                       chain)


def consecutive_slices(iterable):
    def collect_elements(previous_elements, element):
        return previous_elements + [element]

    return accumulate(chain(([],), iterable), collect_elements)

在这里,我们使用chain 作为初始切片添加空list,使用islice 可以在结果中忽略它

from itertools import islice
...
islice(consecutive_slices(range(10)), 1, None)

但将其保留为一个切片似乎是合法的,因为空切片毕竟也是一个切片。

与以前的解决方案相比,这仍然是 4 行代码函数,它做的事情几乎相同,但更少的“意大利面条”IMO。

【讨论】:

    【解决方案2】:

    您可以有一个直接yields 手段的生成器,其中包含运行总数和计数的局部变量。 (实际上,您可以通过迭代enumerate(iterable) 并将1 添加到索引中来免费获得计数。这是否足够提示?

    【讨论】:

    • 在我的情况下,平均值的计算和切片的生成应该是独立的操作
    【解决方案3】:

    看看itertools.islice Link

    import itertools
    def get_slices(iterable):
        return map(lambda x: itertools.islice(iterable, x), xrange(len(iterable)))
    

    如果你不知道长度,这里有一个 reduce 版本,内存效率非常低:

    from functools import reduce
    numbers = (number for number in range(1,100))
    mean = lambda x, y: (x+y)/float(2)
    reduce(lambda x, y: x + [mean(x[-1], y)], numbers, [0])
    [0.0, 0.5, 1.25, 2.125, 3.0625, 4.03125, 5.015625, 6.0078125, 7.00390625, 8.001953125, 9.0009765625, 10.00048828125, 11.000244140625, 12.0001220703125, 13.00006103515625, 14.000030517578125, 15.000015258789062, 16.00000762939453, 17.000003814697266, 18.000001907348633, 19.000000953674316, 20.000000476837158, 21.00000023841858, 22.00000011920929, 23.000000059604645, 24.000000029802322, 25.00000001490116, 26.00000000745058, 27.00000000372529, 28.000000001862645, 29.000000000931323, 30.00000000046566, 31.00000000023283, 32.000000000116415, 33.00000000005821, 34.000000000029104, 35.00000000001455, 36.000000000007276, 37.00000000000364, 38.00000000000182, 39.00000000000091, 40.000000000000455, 41.00000000000023, 42.000000000000114, 43.00000000000006, 44.00000000000003, 45.000000000000014, 46.00000000000001, 47.0, 48.0, 49.0, 50.0, 51.0, 52.0, 53.0, 54.0, 55.0, 56.0, 57.0, 58.0, 59.0, 60.0, 61.0, 62.0, 63.0, 64.0, 65.0, 66.0, 67.0, 68.0, 69.0, 70.0, 71.0, 72.0, 73.0, 74.0, 75.0, 76.0, 77.0, 78.0, 79.0, 80.0, 81.0, 82.0, 83.0, 84.0, 85.0, 86.0, 87.0, 88.0, 89.0, 90.0, 91.0, 92.0, 93.0, 94.0, 95.0, 96.0, 97.0, 98.0]
    

    所以,最后我们所做的几乎与您的代码相同,因此您应该使用它或使用列表而不是生成器,然后使用列表的切片 (itertools.ilice).

    编辑: 我一直在想这个,用Haskellscanl很容易解决,所以我把这个概念概括了,得到了很好的结果:

    def scanl(f, g):
        n = next(g)
        yield n
        for e in g:
            n = f(n, e)
            yield n
    
    list(scanl(mean, number))
    [0, 0.5, 1.25, 2.125, 3.0625, 4.03125, 5.015625, 6.0078125, 7.00390625, 8.001953125, 9.0009765625, 10.00048828125, 11.000244140625, 12.0001220703125, 13.00006103515625, 14.000030517578125, 15.000015258789062, 16.00000762939453, 17.000003814697266, 18.000001907348633, 19.000000953674316, 20.000000476837158, 21.00000023841858, 22.00000011920929, 23.000000059604645, 24.000000029802322, 25.00000001490116, 26.00000000745058, 27.00000000372529, 28.000000001862645, 29.000000000931323, 30.00000000046566, 31.00000000023283, 32.000000000116415, 33.00000000005821, 34.000000000029104, 35.00000000001455, 36.000000000007276, 37.00000000000364, 38.00000000000182, 39.00000000000091, 40.000000000000455, 41.00000000000023, 42.000000000000114, 43.00000000000006, 44.00000000000003, 45.000000000000014, 46.00000000000001, 47.0, 48.0, 49.0, 50.0, 51.0, 52.0, 53.0, 54.0, 55.0, 56.0, 57.0, 58.0, 59.0, 60.0, 61.0, 62.0, 63.0, 64.0, 65.0, 66.0, 67.0, 68.0, 69.0, 70.0, 71.0, 72.0, 73.0, 74.0, 75.0, 76.0, 77.0, 78.0, 79.0, 80.0, 81.0, 82.0, 83.0, 84.0, 85.0, 86.0, 87.0, 88.0, 89.0, 90.0, 91.0, 92.0, 93.0, 94.0, 95.0, 96.0, 97.0, 98.0]
    

    【讨论】:

    • 在我的情况下 iterable 没有 __len__
    • @AzatIbrakov,你不知道长度吗?
    • @AzatIbrakov,你能重复几次吗?还是会消耗iterable?
    • @AzatIbrakov,检查编辑后的答案,你有你正在寻找的解决方案
    • mean 对所有数字都进行计数,如果不存储以前的计数就无法累加,因此您的mean 函数定义不正确
    猜你喜欢
    • 2019-09-18
    • 1970-01-01
    • 1970-01-01
    • 2016-05-31
    • 1970-01-01
    • 1970-01-01
    • 2023-01-08
    • 2015-05-27
    • 2019-07-23
    相关资源
    最近更新 更多