我正在解析这样的文件:
--标题-- 数据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 中通过谓词优雅高效地对可迭代对象进行分组?