merlin2011
merlin2011

Reputation: 75545

Is there a more Pythonic way to conditionally merge adjacent list elements?

Suppose I have the following list.

myList = ["echo FooBar \\", "and some more foos", "cat /etc/cpuinfo", 
    "cat /path/to/\\", "long/deep/directory/\\", "that/goes/on/forever"]

I want to merge any element that ends in a \ with the element on its right, removing the \ in the process, and continue to do this until there are no more elements with a trailing \.

The correct output looks like this.

['echo FooBar and some more foos', 
 'cat /etc/cpuinfo', 
 'cat /path/to/long/deep/directory/that/goes/on/forever']

Here is my current solution, which is functional but seems much more complex than necessary.

myList = ["echo FooBar \\", "and some more foos", "cat /etc/cpuinfo", 
    "cat /path/to/\\", "long/deep/directory/\\", "that/goes/on/forever"]

tmpItem = None
tmpList = []

for x in myList:
   if tmpItem:
     if tmpItem.endswith("\\"):
        tmpItem = tmpItem[:-1] + x
     else:
        tmpList.append(tmpItem)
        tmpItem = x
   else: tmpItem = x

if tmpItem:
     if tmpItem.endswith("\\"):
        tmpList.append(tmpItem[:-1])
     else:
        tmpList.append(tmpItem)

print tmpList

Is there a more concise way to do this in Python, possibly using a more functional idiom?

I looked at reduce(), but it seems to only let you move a reduce from a list to a single element, rather than to another list, but perhaps I am underestimating its power.

Upvotes: 3

Views: 464

Answers (5)

martineau
martineau

Reputation: 123423

It may not be the shortest, but it's fairly readable and Pythonic.

out, grp = [], []
for s in my_list:
    if s.endswith('\\'):
        grp.append(s[:-1])
    else:
        out.append(''.join(grp) + s)
        grp = []

print(out)

Upvotes: 0

Reut Sharabani
Reut Sharabani

Reputation: 31339

If this is pythonic for you:

reduce(lambda agg, x: (agg +
                        [agg.pop()[:-1] + x] if agg[-1].endswith('\\') else
                         agg + [x]) if len(agg) > 0 else
                         agg + [x], myList, [])

I think it's cool and useful to understand (even if not to use)

Explanation: uses the aggregated list in reduce and looks back to the last element to append to it if needed. Otherwise appends to list. Doesn't look back on first element to avoid exception.

['echo FooBar and some more foos', 'cat /etc/cpuinfo', 'cat /path/to/long/deep/directory/that/goes/on/forever']

Upvotes: 1

Kalle Aaltonen
Kalle Aaltonen

Reputation: 126

Not sure if more pythonic, but certainly more concise.

"\0".join(myList).replace("\\\0","").split("\0")

If you can't make assumptions that the strings don't include \0 you can generate the separator:

import string
import random
sep = ""
while any(sep in s for s in myList):
    sep += random.choice(string.ascii_uppercase + string.digits)
sep.join(myList).replace("\\"+sep,"").split(sep)

Upvotes: 3

Doncho Gunchev
Doncho Gunchev

Reputation: 2239

myList = ["echo FooBar \\", "and some more foos", "cat /etc/cpuinfo", 
    "cat /path/to/\\", "long/deep/directory/\\", "that/goes/on/forever"]
ret = []
for i in myList:
    if ret and ret[-1].endswith('\\'):
        ret[-1] = ret[-1][:-1] + i
    else:
        ret.append(i)

print ret

prints

['echo FooBar and some more foos', 'cat /etc/cpuinfo',
 'cat /path/to/long/deep/directory/that/goes/on/forever']

Upvotes: 2

6502
6502

Reputation: 114461

A possibly easier to read solution is

wp = 0
for L in myList:
    if wp and myList[wp - 1].endswith('\\'):
        myList[wp - 1] = myList[wp - 1][:-1] + L
    else:
        myList[wp] = L
        wp += 1
del myList[wp:]

For me this is easier to read because the wp "write-pointer" pattern to modify arrays inplace is something that I've in the fingers. Readiility is in the eye of the beholder...

A purely functional solution (no assignments at all) could be

def merge_continuations(L, ix=0):
    if ix >= len(L)-1:
        return L
    elif L[ix].endswith('\\'):
        return merge_continuations(L[:ix] +
                                   [L[ix][:-1] + L[ix+1]] +
                                   L[ix+2:], ix)
    else:
        return merge_continuations(L, ix+1)

but it really doesn't belong to Python.

Another version could be written as a generator, accepting any iterable:

def merge_continuations(it):
    prefix = None
    for L in it:
        if prefix is None:
            prefix = L
        elif prefix.endswith('\\'):
            prefix = prefix[:-1] + L
        else:
            yield prefix
            prefix = L
    if prefix is not None:
        yield prefix

not my preferred approach, but idiomatic in Python

Upvotes: 0

Related Questions