2

I have the following problem : From a list of strings, i have to take the first letters from all the strings, after (from back to front), i have to take the second letters, after the third letters from front to end and so on.

Example input :

['abcd', 'efgh', 'ijkl', 'mnop']

Output should be :

'aeimnjfbcgkoplhd'

Here I am so far, the first "for" is appending to the array : aeim and cgko the second "for" is appending to the array: njfb and plhd. Anyway the order is not good, i need aeim + njfb + cgko + plhd

array = []
if len(list_of_strings[0]) % 2 == 0: # if we have strings with even number of letters
    for j in range(len(list_of_strings[0]/2)): # range(2) in our example
        for i in range(len(list_of_strings)): # range(4) in our example
            array.append(list_of_strings[i][j*2])

    for j in range(1, len(list_of_strings[0]), 2): # range(1, 4, 2) in our example
        for i in range(len(list_of_strings) - 1, -1, -1): # range(3, -1, -1) in our example
            array.append(list_of_strings[i][j])

Please help.

Thank you

cs95
  • 379,657
  • 97
  • 704
  • 746

2 Answers2

5

You can use a simple one-liner using "unzip" (i.e. zip(*a)) and str.join:

a = ['abcd', 'efgh', 'ijkl', 'mnop']
b = ''.join(''.join(t[::1-2*(i%2)]) for i, t in enumerate(zip(*a)))
assert b == 'aeimnjfbcgkoplhd'

join can take a generator expression as an argument, in this case the generator expression is

''.join(t[::1-2*(i%2)]) for i, t in enumerate(zip(*a))

The expression

zip(*a)

unzips the strings in a, i.e. it returns a generator which yields tuples containing all first letters, all second letters, etc. of each string.

The indexing in

t[::1-2*(i%2)]

ensures that we reverse the order of the tuple every 2nd iteration.


EDIT

I benchmarked my one-liner vs. @cs95's answer, and performance of both is the same within error margins. I think in "real code" I'd hence prefer his solution for its higher clarity.

Community
  • 1
  • 1
Jan Christoph Terasa
  • 5,781
  • 24
  • 34
4

Think of the characters as elements in a 2D array:

a b c d
e f g h
i j k l
m n o p

We want to go in down on odd columns, then up on even columns, so we do something like this:

chars = []
for i in range(len(l[0])):
    for w in l[::1 if i  % 2 == 0 else -1]:  
        chars.append(w[i])
print(''.join(chars))
# aeimnjfbcgkoplhd

l[::1 if i % 2 == 0 else -1] will reverse the list for even columns so we're picking characters from the end. This is intuitive but ugly since slicing the list creates a shallow copy. We can do something a little more clever by using a mod to determine whether to iterate in reverse:

chars = []
for i in range(len(l[0])):
    for j in range(len(l)) if i % 2 == 0 else reversed(range(len(l))):
        chars.append(l[j][i])
print(''.join(chars))
# aeimnjfbcgkoplhd
cs95
  • 379,657
  • 97
  • 704
  • 746
  • Thank you so much for the solution and for the tip of how I should think ! – alex_mucenic Apr 27 '20 at 07:23
  • @alex_mucenic Good effort and nice question, thanks. – cs95 Apr 27 '20 at 07:26
  • @alex_mucenic PS I've added a second solution that shows how you can switch up the direction of iteration for the inner loop *pythonically*, PTAL – cs95 Apr 27 '20 at 07:29
  • 1
    @cs95 The "more clever" solution is actually quite a bit slower than the first one (2.68µs instead of 1.93µs on my machine), at least for the given (short) input. It is probably faster creating `reversed(range(len(l)))` only once. – Jan Christoph Terasa Apr 27 '20 at 11:05
  • @cs95 `range(len(l)-1, -1, -1)` is also faster than reversing the range. – Jan Christoph Terasa Apr 27 '20 at 11:13
  • @JanChristophTerasa thanks for the comments, appreciate the benchmarks. Yes, performance was not an objective here, I wanted to work with OP's loop-based solution here to help them understand where they went wrong. Your answer works too. It's worth testing this on much larger lists of larger strings. – cs95 Apr 27 '20 at 15:43