Объединить элемент списка с предыдущим элементом списка

Я пытаюсь объединить элементы списка с предыдущими элементами, если они не содержат определенного префикса, и добавить 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 каждый раз, когда элемент начинается с префикса.