Мы можем использовать 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
вместо.