Объединить элемент списка с предыдущим элементом списка
Я пытаюсь объединить элементы списка с предыдущими элементами, если они не содержат определенного префикса, и добавить n
между указанными элементами списка при этом.
prefix = '!'
cmds = ['!test','hello','world','!echo','!embed','oh god']
output = ['!testnhellonworld','!echo','!embednoh god']
я попробовал что-то вроде
for i in list(range(0,len(cmds))):
if not cmds[i+1].startswith(prefix):
cmds[i] += cmds.pop(i+1)
но всегда получается list index out of range
ошибка.
мои извинения если это плохо сформулировано или кажется очевидным исправлением, я довольно новичок в python/программировании.
Edit:
мне удалось заставить его работать с
prefix = '!'
cmds = ['!test','hello','world','!echo','!embed','oh god']
print(list(range(0,len(cmds))))
for i in reversed(range(len(cmds))):
if not cmds[i].startswith(prefix):
cmds[i-1] += 'n'+cmds.pop(i)
print(cmds)
но ваши ответы кажутся такими аккуратными и эффективными. Большое спасибо всем
4 ответов
Я предлагаю создать новый список, как вы показали в вашей спецификации проблемы:
prefix = '!'
cmds = ['!test','hello','world','!echo','!embed','oh god']
output = []
for cmd in cmds:
if cmd.startswith(prefix) or not output:
output.append(cmd)
else:
output[-1] += "\n" + cmd # change the string in the last element of output
результат:
>>> output
['!test\nhello\nworld', '!echo', '!embed\noh god']
вот одно линейное решение с использованием itertools.groupby
и itertools.accumulate
:
from itertools import accumulate, groupby
from operator import itemgetter
x = ['!test','hello','world','!echo','!embed','oh god']
cumsum = accumulate(map(lambda s: s.startswith('!'), x))
result = ['\n'.join(map(itemgetter(0), g)) for _, g in groupby(zip(x, cumsum), itemgetter(1))]
это выглядит как два лайнера, потому что я хотел сделать его полупрозрачным, но это не всегда необходимо:
result = ['\n'.join(map(itemgetter(0), g)) for _, g in groupby(zip(x, accumulate(map(lambda s: s.startswith('!'), x))), itemgetter(1))]
cumsum
предоставляет количество элементов, начиная с !
нашел до сих пор. Это делает для хорошего ключа groupby
. Он работает, накапливая логические значения, возвращаемые str.startswith
в целое число.
конечный результат использует cumsum
как ключ, но присоединяется к сгруппированные элементы x
С новой строки.
здесь IDEOne Link чтобы играть.
вы можете сделать это с пониманием списка "также".
In [1]: cmds = ['!test','hello','world','!echo','!embed','oh god']
In [2]: prefix = '!'
In [3]: inds = [i for i, x in enumerate(cmds) if prefix in x]
In [4]: inds.append(len(cmds))
In [5]: lens = list(zip(inds, inds[1:]))
# [(0, 3), (3, 4), (4, 6)]
In [6]: ["\n".join(cmds[a:b]) for a, b in lens]
Out[6]: ['!test\nhello\nworld', '!echo', '!embed\noh god']
решение, которое немного длиннее, но которое можно легко обобщить на другие ситуации, используя itertools.метода groupBy:
from itertools import groupby
class StartGroupOnPrefix:
def __init__(self, prefix):
self.output = False
self.prefix = prefix
def __call__(self, item):
if item.startswith(self.prefix):
self.output = not self.output
return self.output
prefix = '!'
cmds = ['!test','hello','world','!echo','!embed','oh god']
condition = StartGroupOnPrefix(prefix)
out = ['\n'.join(group) for f, group in groupby(cmds, condition)]
print(out)
# ['!test\nhello\nworld','!echo','!embed\noh god']
поскольку у нас есть итератор, нам также не нужно создавать весь список вывода сразу, мы можем генерировать каждый вывод на лету:
for grouped_item in ('\n'.join(group) for f, group in groupby(cmds, condition)):
print('-----------\n', grouped_item)
# -----------
# !test
# hello
# world
# -----------
# !echo
# -----------
# !embed
# oh god
немного объяснений :groupby(iterable)
запускает новую группу каждый раз, когда она получает другой элемент из iterable
. groupby(iterable, key)
запускает новую группу каждый раз, когда возвращается значение key
функции меняются. Наши!--6--> функция чередует свой выход между True
и False
каждый раз, когда элемент начинается с префикса.