0
\$\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

\$\endgroup\$
2
\$\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.

\$\endgroup\$
1
  • \$\begingroup\$ Thanks you for bringing my attention to the zip function, really neat \$\endgroup\$ – 13ros27 Nov 27 '18 at 15:56
0
\$\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

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.