Is there a more Pythonic way to conditionally concatenate adjacent list items?
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 concatenate any element that ends in \
with the element on the right, deleting \
in the process and continuing to do so until there are no more elements ending in \
.
The correct output is as follows.
['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 complicated 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 shorter way to do this in Python, perhaps using a more functional idiom?
I've looked reduce()
, but it looks like you can move the abbreviation from a list to one item rather than another list, but perhaps I am underestimating its power.
source to share
Not sure if more pythonic, but more concise of course.
"\0".join(myList).replace("\\\0","").split("\0")
If you can't make the assumption that the lines don't include \0
, you can generate a delimiter:
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)
source to share
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']
source to share
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 this is cool and useful to understand (even if not used)
Explanation: Uses the aggregated list in reduce
and refers to the last item to add to it if needed. Otherwise, it is added to the list. Doesn't look back at the first element to avoid an exception.
['echo FooBar and some more foos', 'cat / etc / cpuinfo', 'cat / path / to / long / deep / directory / that / go / on / forever']
source to share
It might be easier to read the solution
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:]
It's easier to read for me because the wp
"write-pointer" pattern for modifying arrays in place is what I have in my fingers. Readability 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 doesn't really 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
source to share