根据每个组的第一个元素从列表列表中收集元素

时间:2019-07-11 12:11:04

标签: python list iteration grouping

我有一个清单

mainlist = [['a','online',20],
            ['a','online',22],
            ['a','offline',26],
            ['a','online',28],
            ['a','offline',31],
            ['a','online',32],
            ['a','online',33],
            ['a','offline',34]]

如果第二个元素是'online',下一个'offline'值是第四个元素,我想获取第三个元素的最小值。迭代应该持续到列表末尾。

最终输出应为

outputlist = [['a', 'online', 20, 26], ['a', 'online', 28, 31], ['a', 'online', 32, 34]]

我尝试了以下代码,但对我没有帮助:

from itertools import product

for a, b in product(mainlist,mainlist):
    if a[1] == 'online':
        minvalue=min(a, key=lambda x:x[2])
    if b[1] == 'offline' and b[2] >=minvalue[2]:
        maxvalue=min(b, key=lambda x:x[2])

3 个答案:

答案 0 :(得分:2)

好像您正在寻找连续的“在线”连胜纪录

只需从头到尾迭代列表,并记住何时开始“在线”,然后在下一次“离线”时,将此条纹添加到结果中即可:

mainlist = [['a', 'online', 20], ['a', 'online', 22], ['a', 'offline', 26], ['a', 'online', 28], ['a', 'offline', 31], ['a', 'online', 32], ['a', 'online', 33], ['a', 'offline', 34]]

output = []
first_online = -1
for item, status, num in mainlist:
    if status == 'online':
        if first_online == -1:
            first_online = num
    elif status == 'offline':
        output.append([item, 'online', first_online, num])
        first_online = -1

print(output)

答案 1 :(得分:1)

这是使用iter

的一种方法

例如:

mainlist=iter([['a','online',20],['a','online',22],['a','offline',26],['a','online',28],['a','offline',31],['a','online',32],['a','online',33],['a','offline',34]])

result = []
for i in mainlist:
    if i[1] == 'online':
        result.append(i)
        while True:
            i = next(mainlist)
            if i[1] == "offline":
                result[-1].append(i[-1])
                break

输出:

[['a', 'online', 20, 26], ['a', 'online', 28, 31], ['a', 'online', 32, 34]]

答案 2 :(得分:1)

itertools.groupby的帮助下,我们可以使用itertools.itemgetter对具有相同第二个元素'online''offline'的连续列表进行分组,然后仅收集必要的输出列表:

from itertools import groupby
from operator import itemgetter

mainlist = [['a', 'online', 20],
            ['a', 'online', 22],
            ['a', 'offline', 26],
            ['a', 'online', 28],
            ['a', 'offline', 31],
            ['a', 'online', 32],
            ['a', 'online', 33],
            ['a', 'offline', 34]]
result = []
for key, group in groupby(mainlist, key=itemgetter(1)):
    if key == 'online':
        output = min(group, key=itemgetter(2)).copy()
        # or `output = next(group).copy()` if data is always sorted
    else:
        next_offline = next(group)
        output.append(next_offline[2])
        result.append(output)
print(result)
# [['a', 'online', 20, 26], ['a', 'online', 28, 31], ['a', 'online', 32, 34]]

我发现此版本比此处介绍的其他版本更好,因为代码没有深层嵌套并且不使用“标志”变量。


进一步的改进:

正如Guido van Rossum所说:"Tuples are for heterogeneous data, list are for homogeneous data.",但现在您的列表保留了异构数据。我建议使用namedtuple,这样可以更容易地区分字段。我将使用typed version模块中的typing,但您可以自由使用collections中的模块。例如,它可能看起来像这样:

from typing import NamedTuple


class Record(NamedTuple):
    process: str
    status: str
    time: int


class FullRecord(NamedTuple):
    process: str
    status: str
    start: int
    end: int

通过使用itertools.starmap,我们可以轻松地从您的列表中获取Record的列表:

from itertools import starmap

records = list(starmap(Record, mainlist))
# [Record(process='a', status='online', time=20),
#  Record(process='a', status='online', time=22),
#  Record(process='a', status='offline', time=26),
#  Record(process='a', status='online', time=28),
#  Record(process='a', status='offline', time=31),
#  Record(process='a', status='online', time=32),
#  Record(process='a', status='online', time=33),
#  Record(process='a', status='offline', time=34)]

然后将第一个代码示例包装在generator function中,并替换其中的某些部分以反映输入数据中的更改:

def collect_times(values):
    for key, group in groupby(values, key=Record.status.fget):
        if key == 'online':
            min_online_record = next(group)
        else:
            next_offline_record = next(group)
            yield FullRecord(process=min_online_record.process,
                             status='online',
                             start=min_online_record.time,
                             end=next_offline_record.time)


result = list(collect_times(records))
# [FullRecord(process='a', status='online', start=20, end=26),
#  FullRecord(process='a', status='online', start=28, end=31),
#  FullRecord(process='a', status='online', start=32, end=34)]

就是这样,现在代码看起来比以前更不言自明了。我们可以看到哪个字段在哪里,它们是通过名称而不是索引来引用的。

请注意,在对数据进行排序时,我会写min_online_record = next(group),但是如果并非总是如此,则应该写min_online_record = min(group, key=Record.time.fget)

此外,如果您有兴趣,请注意RecordFullRecord中的字段重复。您可以通过从具有两个字段processstatus的父类继承来避免这种情况,但是从namedtuple继承则是not really pretty。因此,如果这样做,请改用dataclass