迭代访问列表的最“ pythonic”方法是什么?

我是研究僧i 提交于 2020-02-28 03:23:24

我有一个Python脚本,它将一个整数列表作为输入,我需要一次处理四个整数。 不幸的是,我无法控制输入,或者将其作为四元素元组的列表传递。 目前,我正在以这种方式对其进行迭代:

for i in xrange(0, len(ints), 4):
    # dummy op for example code
    foo += ints[i] * ints[i + 1] + ints[i + 2] * ints[i + 3]

不过,它看起来很像“ C思维”,这使我怀疑还有一种处理这种情况的更Python的方法。 该列表在迭代后被丢弃,因此不需要保留。 也许这样的事情会更好?

while ints:
    foo += ints[0] * ints[1] + ints[2] * ints[3]
    ints[0:4] = []

不过,还是不太“正确”。 :-/

相关问题: 如何在Python中将列表分成均匀大小的块?


#1楼

此问题的理想解决方案适用于迭代器(而不仅仅是序列)。 它也应该很快。

这是itertools文档提供的解决方案:

def grouper(n, iterable, fillvalue=None):
    #"grouper(3, 'ABCDEFG', 'x') --> ABC DEF Gxx"
    args = [iter(iterable)] * n
    return itertools.izip_longest(fillvalue=fillvalue, *args)

在Mac book air上使用ipython的%timeit ,每个循环可获得47.5美元。

但是,这对我来说真的不起作用,因为结果被填充为甚至大小的组。 没有填充的解决方案稍微复杂一些。 最幼稚的解决方案可能是:

def grouper(size, iterable):
    i = iter(iterable)
    while True:
        out = []
        try:
            for _ in range(size):
                out.append(i.next())
        except StopIteration:
            yield out
            break

        yield out

简单但很慢:每个循环693 us

我能想到的最佳解决方案是将islice用于内部循环:

def grouper(size, iterable):
    it = iter(iterable)
    while True:
        group = tuple(itertools.islice(it, None, size))
        if not group:
            break
        yield group

使用相同的数据集,每个循环可获得305 us。

无法以比这更快的速度获得纯解决方案,我为以下解决方案提供了一个重要的警告:如果输入数据中包含filldata实例,则可能会得到错误的答案。

def grouper(n, iterable, fillvalue=None):
    #"grouper(3, 'ABCDEFG', 'x') --> ABC DEF Gxx"
    args = [iter(iterable)] * n
    for i in itertools.izip_longest(fillvalue=fillvalue, *args):
        if tuple(i)[-1] == fillvalue:
            yield tuple(v for v in i if v != fillvalue)
        else:
            yield i

我真的不喜欢这个答案,但是速度更快。 每个循环124 us


#2楼

另一个答案,其优点是:

1)容易理解
2)适用于任何可迭代的对象,而不仅仅是序列(上面的某些答案会在文件句柄上阻塞)
3)不会一次将块全部加载到内存中
4)不在内存中对同一迭代器建立一个大块的引用列表
5)列表末尾不填充填充值

话虽如此,我还没有计时,所以它可能比一些更聪明的方法要慢,并且某些优点可能与用例无关。

def chunkiter(iterable, size):
  def inneriter(first, iterator, size):
    yield first
    for _ in xrange(size - 1): 
      yield iterator.next()
  it = iter(iterable)
  while True:
    yield inneriter(it.next(), it, size)

In [2]: i = chunkiter('abcdefgh', 3)
In [3]: for ii in i:                                                
          for c in ii:
            print c,
          print ''
        ...:     
        a b c 
        d e f 
        g h 

更新:
由于内循环和外循环从同一个迭代器中提取值,因此带来了一些缺点:
1)Continue在外部循环中无法按预期工作-它只是继续到下一个项目,而不是跳过一个块。 但是,这似乎不是问题,因为在外循环中没有要测试的东西。
2)break在内部循环中无法正常工作-控件将在迭代器中的下一个项目中再次进入内部循环。 要跳过整个块,可以将内部迭代器(上面的ii)包装在一个元组中,例如for c in tuple(ii)中将for c in tuple(ii)打包,或者设置一个标志并耗尽迭代器。


#3楼

与其他建议类似,但不完全相同,我喜欢这样做,因为它简单易读:

it = iter([1, 2, 3, 4, 5, 6, 7, 8, 9])
for chunk in zip(it, it, it, it):
    print chunk

>>> (1, 2, 3, 4)
>>> (5, 6, 7, 8)

这样,您将不会得到最后的部分块。 如果要获取(9, None, None, None)作为最后一块,只需使用itertools izip_longest


#4楼

使用小功能和事情确实对我没有吸引力。 我更喜欢只使用切片:

data = [...]
chunk_size = 10000 # or whatever
chunks = [data[i:i+chunk_size] for i in xrange(0,len(data),chunk_size)]
for chunk in chunks:
    ...

#5楼

我需要一个可以与集合和生成器一起使用的解决方案。 我无法提出任何简短而又漂亮的内容,但至少可以理解。

def chunker(seq, size):
    res = []
    for el in seq:
        res.append(el)
        if len(res) == size:
            yield res
            res = []
    if res:
        yield res

清单:

>>> list(chunker([i for i in range(10)], 3))
[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]]

组:

>>> list(chunker(set([i for i in range(10)]), 3))
[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]]

发电机:

>>> list(chunker((i for i in range(10)), 3))
[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]]
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!