If you split a string like foobar
with something like [::3], [1::3], [2::3]
it will give you ['fb','oa','or']
. I needed to then be able to rejoin that into foobar
and I got given a challenge to make it in one line. This is my solution:
split_join = lambda t:''.join(''.join([s.ljust(len(max(t,key=len))) for s in t])[i::len(max(t,key=len))] for i in range(len(max(t,key=len)))).replace(' ','')
and I was wondering if there was any neater or shorter way to make this.
EDIT: I also want it to be able to deal with strings of uneven lengths
2 Answers 2
First, in order to make this even remotely readable, let's convert it to a function and save intermediate results (especially the reused ones) to variables:
def split_join6(table):
cols = max(map(len, table))
justified = ''.join([col.ljust(cols) for col in table])
y = ''.join(justified[i::cols] for i in range(cols))
return y.replace(' ','')
Now, what you seem to want is similar to the roundrobin
recipe from itertools
:
from itertools import cycle, islice def roundrobin(*iterables): "roundrobin('ABC', 'D', 'EF') --> A D E B F C" # Recipe credited to George Sakkis num_active = len(iterables) nexts = cycle(iter(it).__next__ for it in iterables) while num_active: try: for next in nexts: yield next() except StopIteration: # Remove the iterator we just exhausted from the cycle. num_active -= 1 nexts = cycle(islice(nexts, num_active))
x = ['fb','oa','or']
print("".join(roundrobin(*x))
# foobar
Note that making things into one-liners can only get you so far. It does sometimes help you to learn some new concepts in a language, but quite often it makes your code unreadable. In Python you should keep your lines to 80 or 120 characters (as per Python's official style-guide, PEP8). Anything that does not fit into that is probably too complicated to understand again, even a month later.
That being said, here is a shorter one-liner, albeit with one needed import:
from itertools import zip_longest
f = lambda x: "".join(map(lambda t: "".join(filter(None, t)), zip_longest(*x)))
f(['fb','oa','or'])
# 'foobar'
The zip_longest
and filter(None, ...)
are only needed in case not all parts are the same length. Otherwise (which is at least true for "foobar"
) it would just be:
f = lambda x: "".join(map("".join, zip(*x)))
Both use the well-known trick of doing zip(*iterable)
to transpose an iterable of iterables.
-
\$\begingroup\$ Thanks you for bringing my attention to the zip function, really neat \$\endgroup\$13ros27– 13ros272018年11月27日 15:56:10 +00:00Commented Nov 27, 2018 at 15:56
Using what @Graipher said and some other things I found my new solution is:
split_join = lambda t:''.join(sum(zip(*[s.ljust(len(max(t,key=len))) for s in t]),())).replace(' ','')
If I find any better way I will update this code