私はこのようなファイルを解析しています:
- ヘッダ - データ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
ソリューションは機能しないようです。隣接するヘッダーが存在する可能性があり、それらは別々のグループに入る必要があるためです。私が思いつくことができる最善の方法groupby
は、カウンターを保持するキー関数を (ab) 使用することです。
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), ラムダ x: x % 1000 == 0), maxlen=0) CPU 時間: ユーザー 4.20 秒、sys: 0.03 秒、合計: 4.23 秒 %time deque(igroup(xrange(10 ** 7), ラムダ x: x % 1000 == 0), maxlen=0) CPU 時間: ユーザー 5.45 秒、sys: 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 で述語によってイテラブルをエレガントかつ効率的にサブグループ化するにはどうすればよいでしょうか?