将元组列表中的一系列条目提取到子列表中的优雅方法是什么?

What's an elegant way to extract a series of entries in a list of tuples into sublists?

假设我在这样的元组列表中有一系列条目:

TRUE = 1
listOfTuples = [('selectable', 'frequency'), ('color', 'green'), ('item', '10 Hz'), 
                ('value', 10), ('align', 'left'), ('hidden', TRUE), ('item', '20 Hz'), 
                ('value', 20), ('align', 'right'), ('item', '50 Hz'), ('value', 50), 
                ('item', '100 Hz'), ('value', 100), ('textColor', 0xFF0000)]

现在我想提取一个包含单个项目条目的列表,如下所示:

[(('item', '10 Hz'), ('value', 10), ('align', 'left'), ('hidden', TRUE)),
 (('item', '20 Hz'), ('value', 20), ('align', 'right')),
 (('item', '50 Hz'), ('value', 50)), 
 (('item', '100 Hz'), ('value', 100), ('textColor', '0xFF0000'))]

用于标识子列表的定界关键字始终是 item 或列表末尾。两个相邻的分隔符之间可以有任意数量的元组。 item 第一次出现之前的列表内容将被忽略。关键字item的检测应该不区分大小写。

我不是 Python 爱好者,所以我不知道如何应用列表理解之类的东西(如果这真的可能的话),因为我需要在分隔符之间提取列表。当然,我可以通过遍历列表、识别关键字在元组中的位置然后提取子列表来以行人的方式完成,但我希望有一个更优雅的解决方案。

您可以使用 tuple() 函数将列表转换为元组,这样您就可以将 listOfTuples 变量中的所有元组附加到您需要的输出中:

TRUE = 1
lot = [('selectable', 'frequency'), ('color', 'green'), ('item', '10 Hz'), 
                ('value', 10), ('align', 'left'), ('hidden', TRUE), ('item', '20 Hz'), 
                ('value', 20), ('align', 'right'), ('item', '50 Hz'), ('value', 50), 
                ('item', '100 Hz'), ('value', 100), ('textColor', 0xFF0000)]

l = [[]]
for i in lot:
    if i[0]=='item':
        l[-1] = tuple(l[-1])
        l.append([])
    l[-1].append(i)
print(l[1:])

输出:

[(('item', '10 Hz'), ('value', 10), ('align', 'left'), ('hidden', 1)), (('item', '20 Hz'), ('value', 20), ('align', 'right')), (('item', '50 Hz'), ('value', 50)), [('item', '100 Hz'), ('value', 100), ('textColor', 16711680)]]

此方法唯一的缺点是需要删除元组输出列表的第一个元素,因此在某些情况下可能不起作用。

您可以使用 itertools.groupby 完成任务:

from itertools import accumulate, groupby

TRUE = 1
listOfTuples = [
    ("selectable", "frequency"),
    ("color", "green"),
    ("item", "10 Hz"),
    ("value", 10),
    ("align", "left"),
    ("hidden", TRUE),
    ("item", "20 Hz"),
    ("value", 20),
    ("align", "right"),
    ("item", "50 Hz"),
    ("value", 50),
    ("item", "100 Hz"),
    ("value", 100),
    ("textColor", 0xFF0000),
]


a = accumulate(t == "item" for t, *_ in listOfTuples)

out = []
for _, g in groupby(zip(a, listOfTuples), lambda k: k[0]):
    l = tuple(t for _, t in g)
    if l[0][0] == "item":
        out.append(l)

print(out)

打印:

[
    (("item", "10 Hz"), ("value", 10), ("align", "left"), ("hidden", 1)),
    (("item", "20 Hz"), ("value", 20), ("align", "right")),
    (("item", "50 Hz"), ("value", 50)),
    (("item", "100 Hz"), ("value", 100), ("textColor", 16711680)),
]

好的,所以与此同时我学习了更多关于列表理解、_* 的使用以及 for 循环中的多个索引。此外,一位好朋友提出了以下解决方案:

indices = [i for i, value in enumerate(listOfTuples) 
           if value[0].casefold() == "item"] + [len(listOfTuples)]
out = [listOfTuples[i:j] for i,j in zip(indices[:-1], indices[1:])]

Indices 包含所有出现的 item 和列表中最后一项的索引。以移位的方式使用 indices (zip(indices[:-1], indices[1:])) 可以直接构建 out.

一种没有条件的建设性方法。使用 itertools.groupby'item' 分组。分组是一个 True/False 分类过程( 理想情况下会引发 True/False 值的交替序列!),因此您可以按 2 块切片并将它们链接在一起.

split-right (initial) 条件意味着第一个 item 的组在“右边”找到,什么在“左边” " 可以忘记([1:]-部分)。

import itertools as it

listOfTuples = # see question

# group by item & discard 1st group due to split-right condition
lst = tuple(tuple(grps) for _, grps in it.groupby(listOfTuples, lambda p: p[0] == 'item'))[1:]

# chain the slices
lst_new = [tuple(it.chain.from_iterable(lst[2*i:2*(i+1)])) for i in range(len(lst)//2)]

print(lst_new)

相同的想法,但使用了生成器

...

lst = (tuple(grps) for _, grps in it.groupby(listOfTuples, lambda p: p[0] == 'item'))
next(l) # split-right initial condition
l1, l2 = it.tee(lst)
n = len(tuple(l2))

lst_new = [tuple(it.chain.from_iterable(it.islice(l1, 0, 2, None))) for _ in range(n//2)]

一种(逻辑不同)基于索引的方法

...
# indices of the items
iter_ = filter(None, (tuple(grps)[0][0] if check else None for check, grps in it.groupby(enumerate(listOfTuples), lambda p: p[1][0] == 'item')))
# zip-stuffs
it1, it2 = it.tee(iter_)
next(it2)
it2 = it.chain(it2, iter((len(listOfTuples),)))
# apply the slices and cast to tuples
lst_new = list(map(tuple, map(listOfTuples.__getitem__, it.starmap(slice, zip(it1, it2)))))

print(lst_new)