如何优雅地生成可迭代对象的所有前缀? (累积迭代)
How to elegantly generate all prefixes of an iterable? (cumulative iterable)
从一个可迭代对象,我想生成一个可迭代对象的前缀(包括原始可迭代对象本身)。
for prefix in prefixes(range(5)):
print(tuple(prefix))
应该导致
(0,)
(0, 1)
(0, 1, 2)
(0, 1, 2, 3)
(0, 1, 2, 3, 4)
或在
()
(0,)
(0, 1)
(0, 1, 2)
(0, 1, 2, 3)
(0, 1, 2, 3, 4)
和
for prefix in prefixes('Hello'):
print(''.join(prefix))
应该导致
H
He
Hel
Hell
Hello
或在
H
He
Hel
Hell
Hello
(空前缀是否是结果的一部分对我来说并不重要,内部或外部结果迭代的确切类型也不重要。)
我能够设计出几种方法来实现这一点,但都感觉至少有点笨拙:
使用切片和长度:
(如果可迭代对象是一个序列则有效)
def prefixes(seq):
for i in range(len(seq)):
yield seq[:i + 1]
或使用列表理解:
def prefixes(seq):
return [seq[:i + 1] for i in range(len(seq))]
... 或生成器表达式
def prefixes(seq):
return (seq[:i + 1] for i in range(len(seq)))
(这些不会产生空前缀。要包含它,请将 [i + 1]
替换为 [i]
并将 range(len(seq))
替换为 range(len(seq) + 1)
以上任何一项。 )
这些感觉很笨重:
- 因为它们不适用于所有类型的可迭代输入
- 因为需要
+ 1
偏移量
- 在某事的
len
上调用 range
(虽然 enumerate
不会使这里变得更好)
使用连接
def prefixes(iterable):
result = ()
for elem in iterable:
result += (elem,)
yield result
(不包括空前缀。这可以通过在 for
循环之前产生一次 result
来更改。)
或使用itertools.accumulate
from itertools import accumulate as acc
def prefixes(iterable):
return acc(iterable, lambda t, elem: t + (elem,), initial=())
或更具可读性:
from itertools import accumulate
def _append(iterable, elem):
return iterable + (elem,)
def prefixes(iterable):
return accumulate(iterable, _append, initial=())
(这两个包括空前缀。Drop it 如果不需要。)
这些感觉很笨重,因为需要将元素打包到长度为一的容器中,只是为了将它们连接到现有的容器中。
更优雅的解决方案?
我觉得我一定是从 itertools
, functools
, operator
or more-itertools
that would allow for a slightly or even significantly less clunky implementation. I mean, this is eerily similar to more_itertools.powerset
中遗漏了一些东西,只是其中的一个相当具体的子集。
这不是很充实,也有点傻:
def prefixes(iterable):
from itertools import tee, islice
iterator = iter(iterable)
length = len(iterable)
for slice_length, it in enumerate(tee(iterator, length), start=1):
yield islice(it, slice_length)
for prefix in prefixes(range(5)):
print(tuple(prefix))
for prefix in prefixes("Hello"):
print("".join(prefix))
输出:
(0,)
(0, 1)
(0, 1, 2)
(0, 1, 2, 3)
(0, 1, 2, 3, 4)
H
He
Hel
Hell
Hello
你最终制作了 n+1
个可迭代的独立迭代器。你还需要提前知道iterable的长度,或者能够获取它的长度(所以你不能传入一个生成器。)
类似于您的第一个串联示例,但构建的是列表而不是元组:
def prefixes(iterable):
result = []
for elem in iterable:
result.append(elem)
yield result
这消除了创建临时单元素元组的必要性。
以任何通用的方式编写 prefixes
函数可能被认为是优雅的,把它放在一个模块中,然后在需要它的代码中导入它,这样它就不会不管它是如何实现的。
另一方面,与不那么通用但更适合特定用例的简短本地函数相比,要求额外导入可能被认为不够优雅。
这是一个可能的非常通用的解决方案:
def prefixes(iterable):
return itertools.accumulate(map(lambda x: (x,), iterable))
它被认为优雅的原因有:
- 它使用了标准库中已有的函数并实现了主要目标,
- 它没有明确提到
accumulate
已经隐含地进行的连接,
- 它不需要
accumulate
的 initial
参数。
但有些人发现使用 map
和 lambda
不如 for
循环优雅。
从一个可迭代对象,我想生成一个可迭代对象的前缀(包括原始可迭代对象本身)。
for prefix in prefixes(range(5)):
print(tuple(prefix))
应该导致
(0,)
(0, 1)
(0, 1, 2)
(0, 1, 2, 3)
(0, 1, 2, 3, 4)
或在
()
(0,)
(0, 1)
(0, 1, 2)
(0, 1, 2, 3)
(0, 1, 2, 3, 4)
和
for prefix in prefixes('Hello'):
print(''.join(prefix))
应该导致
H
He
Hel
Hell
Hello
或在
H
He
Hel
Hell
Hello
(空前缀是否是结果的一部分对我来说并不重要,内部或外部结果迭代的确切类型也不重要。)
我能够设计出几种方法来实现这一点,但都感觉至少有点笨拙:
使用切片和长度:
(如果可迭代对象是一个序列则有效)
def prefixes(seq):
for i in range(len(seq)):
yield seq[:i + 1]
或使用列表理解:
def prefixes(seq):
return [seq[:i + 1] for i in range(len(seq))]
... 或生成器表达式
def prefixes(seq):
return (seq[:i + 1] for i in range(len(seq)))
(这些不会产生空前缀。要包含它,请将 [i + 1]
替换为 [i]
并将 range(len(seq))
替换为 range(len(seq) + 1)
以上任何一项。 )
这些感觉很笨重:
- 因为它们不适用于所有类型的可迭代输入
- 因为需要
+ 1
偏移量 - 在某事的
len
上调用range
(虽然enumerate
不会使这里变得更好)
使用连接
def prefixes(iterable):
result = ()
for elem in iterable:
result += (elem,)
yield result
(不包括空前缀。这可以通过在 for
循环之前产生一次 result
来更改。)
或使用itertools.accumulate
from itertools import accumulate as acc
def prefixes(iterable):
return acc(iterable, lambda t, elem: t + (elem,), initial=())
或更具可读性:
from itertools import accumulate
def _append(iterable, elem):
return iterable + (elem,)
def prefixes(iterable):
return accumulate(iterable, _append, initial=())
(这两个包括空前缀。Drop it 如果不需要。)
这些感觉很笨重,因为需要将元素打包到长度为一的容器中,只是为了将它们连接到现有的容器中。
更优雅的解决方案?
我觉得我一定是从 itertools
, functools
, operator
or more-itertools
that would allow for a slightly or even significantly less clunky implementation. I mean, this is eerily similar to more_itertools.powerset
中遗漏了一些东西,只是其中的一个相当具体的子集。
这不是很充实,也有点傻:
def prefixes(iterable):
from itertools import tee, islice
iterator = iter(iterable)
length = len(iterable)
for slice_length, it in enumerate(tee(iterator, length), start=1):
yield islice(it, slice_length)
for prefix in prefixes(range(5)):
print(tuple(prefix))
for prefix in prefixes("Hello"):
print("".join(prefix))
输出:
(0,)
(0, 1)
(0, 1, 2)
(0, 1, 2, 3)
(0, 1, 2, 3, 4)
H
He
Hel
Hell
Hello
你最终制作了 n+1
个可迭代的独立迭代器。你还需要提前知道iterable的长度,或者能够获取它的长度(所以你不能传入一个生成器。)
类似于您的第一个串联示例,但构建的是列表而不是元组:
def prefixes(iterable):
result = []
for elem in iterable:
result.append(elem)
yield result
这消除了创建临时单元素元组的必要性。
以任何通用的方式编写 prefixes
函数可能被认为是优雅的,把它放在一个模块中,然后在需要它的代码中导入它,这样它就不会不管它是如何实现的。
另一方面,与不那么通用但更适合特定用例的简短本地函数相比,要求额外导入可能被认为不够优雅。
这是一个可能的非常通用的解决方案:
def prefixes(iterable):
return itertools.accumulate(map(lambda x: (x,), iterable))
它被认为优雅的原因有:
- 它使用了标准库中已有的函数并实现了主要目标,
- 它没有明确提到
accumulate
已经隐含地进行的连接, - 它不需要
accumulate
的initial
参数。
但有些人发现使用 map
和 lambda
不如 for
循环优雅。