1
\$\begingroup\$

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

asked Nov 27, 2018 at 15:05
\$\endgroup\$

2 Answers 2

3
\$\begingroup\$

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.

answered Nov 27, 2018 at 15:20
\$\endgroup\$
1
  • \$\begingroup\$ Thanks you for bringing my attention to the zip function, really neat \$\endgroup\$ Commented Nov 27, 2018 at 15:56
1
\$\begingroup\$

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

answered Nov 27, 2018 at 19:00
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.