【发布时间】:2012-10-08 04:43:37
【问题描述】:
我正在解析这样的文件:
--标题-- 数据1 数据2 --标题-- 数据3 数据4 数据5 --标题-- --标题-- ...我想要这样的组:
[ [header, data1, data2], [header, data3, data4, data5], [header], [header], ... ]
所以我可以像这样迭代它们:
for grp in group(open('file.txt'), lambda line: 'header' in line):
for item in grp:
process(item)
并将检测组逻辑与处理组逻辑分开。
但我需要一个可迭代的可迭代对象,因为这些组可以任意大,我不想存储它们。也就是说,每次遇到“哨兵”或“标题”项目时,我都想将可迭代对象拆分为子组,如谓词所示。似乎这将是一项常见任务,但我找不到有效的 Pythonic 实现。
这是一个愚蠢的追加到列表的实现:
def group(iterable, isstart=lambda x: x):
"""Group `iterable` into groups starting with items where `isstart(item)` is true.
Start items are included in the group. The first group may or may not have a
start item. An empty `iterable` results in an empty result (zero groups)."""
items = []
for item in iterable:
if isstart(item) and items:
yield iter(items)
items = []
items.append(item)
if items:
yield iter(items)
感觉必须有一个不错的itertools 版本,但它让我望而却步。 '明显' (?!) groupby 解决方案似乎不起作用,因为可能存在相邻的标题,并且它们需要分成不同的组。我能想到的最好的方法是(ab)使用groupby 和一个保持计数器的关键功能:
def igroup(iterable, isstart=lambda x: x):
def keyfunc(item):
if isstart(item):
keyfunc.groupnum += 1 # Python 2's closures leave something to be desired
return keyfunc.groupnum
keyfunc.groupnum = 0
return (group for _, group in itertools.groupby(iterable, keyfunc))
但我觉得 Python 可以做得更好——遗憾的是,这比哑列表版本还要慢:
#ipython %time deque(group(xrange(10 ** 7), lambda x: x % 1000 == 0), maxlen=0) CPU 时间:用户 4.20 秒,系统:0.03 秒,总计:4.23 秒 %time deque(igroup(xrange(10 ** 7), lambda x: x % 1000 == 0), maxlen=0) CPU 时间:用户 5.45 秒,系统:0.01 秒,总计:5.46 秒为了方便您,这里有一些单元测试代码:
class Test(unittest.TestCase):
def test_group(self):
MAXINT, MAXLEN, NUMTRIALS = 100, 100000, 21
isstart = lambda x: x == 0
self.assertEqual(next(igroup([], isstart), None), None)
self.assertEqual([list(grp) for grp in igroup([0] * 3, isstart)], [[0]] * 3)
self.assertEqual([list(grp) for grp in igroup([1] * 3, isstart)], [[1] * 3])
self.assertEqual(len(list(igroup([0,1,2] * 3, isstart))), 3) # Catch hangs when groups are not consumed
for _ in xrange(NUMTRIALS):
expected, items = itertools.tee(itertools.starmap(random.randint, itertools.repeat((0, MAXINT), random.randint(0, MAXLEN))))
for grpnum, grp in enumerate(igroup(items, isstart)):
start = next(grp)
self.assertTrue(isstart(start) or grpnum == 0)
self.assertEqual(start, next(expected))
for item in grp:
self.assertFalse(isstart(item))
self.assertEqual(item, next(expected))
那么:如何在 Python 中通过谓词优雅高效地对可迭代对象进行子分组?
【问题讨论】:
-
您的“附加到列表”版本与您所说的不一致。它将源迭代中的每个项目生成为一个项目列表。你能澄清你想要做什么吗?为什么不举一个例子说明你打算如何使用结果(即,你打算用嵌套的 for 循环或什么来迭代它)?
-
@BrenBarn:生成器将
[1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0]转换为[[1, 0, 0, 0], [1, 0, 0], [1, 0], [1, 0]]。 -
啊,我明白了,我没有注意到默认的
isstart在做什么。但是最好有一个你希望如何使用它的例子。 -
@BrenBarn:当元素表示一个部分时,第二个参数返回
True,因此对于那个特定示例,我使用了igroup(l, lambda x: x == 1))。我想列表版本的行为相同。 -
你说得对,我不是很清楚;我添加了示例用法,也使示例更加困难。 :)
标签: python performance iterator