Собирать элементы из списка списков на основе первых элементов каждой группы - PullRequest
1 голос
/ 11 июля 2019

У меня есть список

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]]

Я хочу получить минимум 3-го элемента, если 2-й элемент равен 'online', а следующее значение 'offline' - 4-й элемент. Итерация должна происходить до конца списка.

Окончательный результат должен быть

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 ]

2 голосов
/ 11 июля 2019

кажется, что вы ищете последовательную серию «онлайн»

просто повторяйте список от начала до конца и помните, когда началось «онлайн», и при следующем «автономном» добавьте эту полосу к результату:

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 голос
/ 11 июля 2019

Мы можем использовать itertools.groupby для группировки последовательных списков, имеющих одинаковые 2-ые элементы, 'online' или 'offline', с помощью itertools.itemgetter, а затем простособерите необходимые выходные списки:

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]]

Я считаю эту версию лучше, чем другие, представленные здесь, поскольку код не глубоко вложен и не использует переменные "flag".


Дальнейшие улучшения:

Как сказал Гвидо ван Россум: " Кортежи предназначены для разнородных данных, список предназначен для однородных данных. " Носейчас в ваших списках хранятся разнородные данные.Я предлагаю использовать namedtuple, что позволяет легче различать поля.Я собираюсь использовать печатную версию из 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

Мы можем легко получить список Record s из вашего списка списков, используя itertools.starmap:

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)]

, а затем давайте обернем первый пример кода в функцию генератора и заменим некоторые его части, чтобы отразить изменения во входных данных:

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).

Кроме того, если вам интересно,обратите внимание, что есть дублирование полей в Record и FullRecord.Вы можете обойти это, наследуя от родительского класса с двумя полями process и status, но наследуя от namedtuple это не очень красиво .Так что, если вы сделаете это, используйте dataclass вместо.

1 голос
/ 11 июля 2019

Это один подход с использованием iter

Ex:

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]]
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...