5

For example, having the string:

abcdefghijklmnopqrstuvwxyz

should result in something like this:

badcfehgjilknmporqtsvuxwzy

How do I even go about it?

I thought of something not very efficient, such as:

s = str(range(ord('a'), ord('z') + 1))
new_s = ''
for i in xrange(len(s)):
    if i != 0 and i % 2 == 0:
        new_s += '_' + s[i]
    else:
        new_s += s[i]
# Now it should result in a string such as 'ab_cd_ef_...wx_yz'
l = new_s.split('_')
for i in xrange(len(l)):
    l[i] = l[i][::-1]
result = str(l)

Is there any better way ? Some way that is more efficient or more general so I could also go about it with 3 letters more easily ?

Ashwini Chaudhary
  • 244,495
  • 58
  • 464
  • 504
Jack
  • 111
  • 1
  • 1
  • 6

6 Answers6

7

You can use zip() function which woud return a list of tuples as [(b,a), (d,c), ...] and the applying .join() method to both the elements of the tuple and list as well.

a = "abcdefghijklmnopqrstuvwxyz"
# a[::2] = "acegikmoqsuwy"
# a[1::2] = "bdfhjlnprtvx"
print "".join("".join(i) for i in zip(a[1::2], a[::2]))
>>> badcfehgjilknmporqtsvuxwzy

EDIT: To handle the case of odd length strings, as suggested by @Ashwini and @TigerhawkT3, you may change the code as:

print "".join("".join(i) for i in zip(a2, a1)) + a[-1] if len(a)%2 else '' 
ZdaR
  • 22,343
  • 7
  • 66
  • 87
3

One solution without using any imports will be to convert string to an iterator and during the iteration fetch the next character by calling next on the iterator:

>>> s = "abcdefghijklmnopqrstuvwxyz"
>>> it = iter(s)
>>> ''.join(next(it, '') + c for c in it )
'badcfehgjilknmporqtsvuxwzy'

Timings:

>>> s = "abcdefghijklmnopqrstuvwxyz" * 10**5
>>> def func_next_no_cache(s):
    it = iter(s)
    return ''.join([next(it, '') + c for c in it])
...
>>> %timeit func_next_no_cache(s)
1 loops, best of 3: 291 ms per loop

But the calls to next are actually slowing it down because for finding next Python has to go to the builtins starting from local scope, let's cache it and try again:

>>> def func_next_cache(s, next=next):
    it = iter(s)
    return ''.join([next(it, '') + c for c in it])
...
>>> %timeit func_next_cache(s)
1 loops, best of 3: 241 ms per loop

But the fastest solution will be to use itertools.izip_longest:

>>> from itertools import izip_longest
>>> def func_izip_l(s):
    it = iter(s)
    return "".join([b+a for a, b in  izip_longest(it, it, fillvalue='')])
...
>>> %timeit func_izip_l(s)

1 loops, best of 3: 209 ms per loop

@Joran's code is also very close to this one when used with a list instead of generator expression, but it creates two additional strings in memory:

>>> %timeit "".join([b+a for a, b in izip_longest(s[::2], s[1::2], fillvalue="")])
1 loops, best of 3: 212 ms per loop

Note that we should always feed a list to str.join if speed is a concern: https://stackoverflow.com/a/9061024/846892

Community
  • 1
  • 1
Ashwini Chaudhary
  • 244,495
  • 58
  • 464
  • 504
2

I'm not sure that reaching for regular expressions first is always the best thing to do, but it seems to fit here. Find 2 characters, sub them in reverse order, and continue until you're out of string.

import re

>>> s = "abcdefghijklmnopqrstuvwxyz"
>>> re.sub(r'(.)(.)', "\g<2>\g<1>", s)
'badcfehgjilknmporqtsvuxwzy'

Easily generalized to other numbers of characters:

>>> def swap3(txt):
...    return re.sub(r'(.)(.)(.)', '\g<3>\g<2>\g<1>', txt)
...
>>> swap3(s)
'cbafedihglkjonmrqputsxwvyz'

or

>>> def parameterizedSwap(txt, numChars):
...    pat = r"(.)" * numChars
...    replace = "".join(["\g<{0}>".format(numChars-i) for i in range(numChars)])
...    return re.sub(pat, replace, txt)
...
>>> parameterizedSwap(s, 5)
'edcbajihgfonmlktsrqpyxwvuz'
bgporter
  • 35,114
  • 8
  • 59
  • 65
  • 1
    agreed that, although chunking and zip is the approach I first though of, regex is easier to read and more extensible. – Adam Smith Jun 03 '15 at 19:00
  • yeah thats why I upvoted this ... nice and short and very easy to read (maybe make the 2nd argument optional (.?) to handle odd length strings ?) – Joran Beasley Jun 03 '15 at 19:05
1
from itertools import izip_longest as myzip
"".join(b+a for a,b in myzip(a[::2],a[1::2],fillvalue=""))

this is very similar to the other answers just some what more explicit in explaining what it is doing to the reader of the code

Ashwini Chaudhary
  • 244,495
  • 58
  • 464
  • 504
Joran Beasley
  • 110,522
  • 12
  • 160
  • 179
0
from itertools import zip, chain

c1 = [c for i, c in enumerate(s) if i % 2 == 0]
c2 = [c for i, c in enumerate(s) if i % 2 == 1]
''.join(chain.from_iterable(zip(c2,c1)))
Andrey
  • 59,039
  • 12
  • 119
  • 163
0

Iterating over pairs of characters and joining them with izip() is fairly simple, and the handling of odd string lengths can be dealt with by adding a conditional concatenation to the end.

from itertools import izip

s = "abcdefghijklmnopqrstuvwxyz"
print ("".join(((pair[1]+pair[0]) for pair in izip(*[iter(s)]*2))) +
            (s[-1] if len(s) % 2 else ''))

The same thing can be done a little more succinctly by using izip_longest() instead of izip(), as @Ashwini suggests in the comment.

from itertools import izip_longest

s = "abcdefghijklmnopqrstuvwxyz"
print "".join(((pair[1]+pair[0]) for pair in
                    izip_longest(*[iter(s)]*2, fillvalue='')))
martineau
  • 119,623
  • 25
  • 170
  • 301