Joseph Keen
Joseph Keen

Reputation: 81

Merge list item with previous list item

I'm trying to merge list items with previous items if they don't contain a certain prefix, and adding a \n between said list items when doing so.

prefix  = '!'
cmds    = ['!test','hello','world','!echo','!embed','oh god']

output  = ['!test\nhello\nworld','!echo','!embed\noh god']

I tried something like

for i in list(range(0,len(cmds))):
    if not cmds[i+1].startswith(prefix):
        cmds[i] += cmds.pop(i+1)

but always get the list index out of range error.

My apologies if this is badly worded, or seems like an obvious fix, I'm fairly new to python/programming.

Edit:

I had managed to get it to work with

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)

but your answers seem so much neater and efficient. Much thanks everyone

Upvotes: 7

Views: 979

Answers (4)

Mad Physicist
Mad Physicist

Reputation: 114310

Here's a one liner solution using itertools.groupby and 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))]

This looks like a two liner because I wanted to make it semi-legible, but that's not always necessary:

result = ['\n'.join(map(itemgetter(0), g)) for _, g in groupby(zip(x, accumulate(map(lambda s: s.startswith('!'), x))), itemgetter(1))]

cumsum provides the number of elements starting with ! found so far. This makes for a nice key to groupby. It works by accumulating the booleans returned by str.startswith into an integer.

The final result uses cumsum as the key, but joins the grouped elements of x with newlines.

Here's an IDEOne Link to play with.

Upvotes: 3

Thierry Lathuille
Thierry Lathuille

Reputation: 24232

A solution that is a bit longer, but that can easily be generalized to other situations, using 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']

As we have an iterator, we also don't have to create the whole output list at once, we can generate each output on-the-fly:

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

A bit of explanation :groupby(iterable) starts a new group each time it gets a different item from iterable. groupby(iterable, key) starts a new group everytime the return value of the key function changes. Our condition function alternates its output between True and False each time the item starts with the prefix.

Upvotes: 1

Praveen
Praveen

Reputation: 9335

You can do it with list comprehension "also".

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

Upvotes: 1

L3viathan
L3viathan

Reputation: 27283

I propose to create a new list, as you showed in your problem specification:

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

The result is:

>>> output
['!test\nhello\nworld', '!echo', '!embed\noh god']

Upvotes: 10

Related Questions