43

Say you have this string:

ABCDEFGH

And you want to reverse it so that it becomes:

GHEFCDAB

What would be the most efficient / pythonic solution? I've tried a few different things but they all look horrible...

Thanks in advance!

Update:

In case anyone's interested, this wasn't for homework. I had a script that was processing data from a network capture and returning it as a string of hex bytes. The problem was the data was still in network order. Due to the way the app was written, I didn't want to go back through and try to use say socket.htons, I just wanted to reverse the string.

Unfortunately my attempts seemed so hideous, I knew there must be a better way (a more pythonic solution) - hence my question here.

6
  • 2
    Could we see what you've tried, maybe we can help improve it. Commented May 3, 2011 at 1:52
  • I'm afraid I don't have it handy (it's at work) but it is truly hideious. At the moment I'm using a very weird loop construct. I would be happy for a simple solution to replace my mess :) Commented May 3, 2011 at 1:55
  • What's the correct result if the input contains an odd number of characters? Commented May 3, 2011 at 1:55
  • 1
    The input will never contain an odd number as they are byte sequences. Commented May 3, 2011 at 1:59
  • Also, no this isn't homework. Commented May 3, 2011 at 2:00

14 Answers 14

40

A concise way to do this is:

"".join(reversed([a[i:i+2] for i in range(0, len(a), 2)]))

This works by first breaking the string into pairs:

>>> [a[i:i+2] for i in range(0, len(a), 2)]
['AB', 'CD', 'EF', 'GH']

then reversing that, and finally concatenating the result back together.

Sign up to request clarification or add additional context in comments.

2 Comments

lol I spent a minute writing about 12 lines out, then I refreshed the page and saw yours.
Moving the reversed inside would be more efficient, then this would one build one list instead of two because join can directly use yours instead of building another from your reversed iterator: "".join([a[i:i+2] for i in reversed(range(0, len(a), 2))])
15

Lots of fun ways to do this

>>> s="ABCDEFGH"
>>> "".join(map(str.__add__, s[-2::-2] ,s[-1::-2]))
'GHEFCDAB'

2 Comments

ohhhh lovely. Yes, as he says, lots of fun ways to do this
+1. This one is pretty neat as it splits and reverses in one go, and doesn't use the length of the string.
14

If anybody is interested, this is the timing for all* the answers.

EDIT (had got it wrong the first time):

import timeit
import struct

string = "ABCDEFGH"

# Expected resutlt => GHEFCDAB

def rev(a):
    new = ""

    for x in range(-1, -len(a), -2):
        new += a[x-1] + a[x]

    return new

def rev2(a):
    return "".join(reversed([a[i:i+2] for i in range(0, len(a), 2)]))

def rev3(a):
    return "".join(map(str.__add__, a[-2::-2] ,a[-1::-2]))

def rev4(a):
    return "".join(map("".join, reversed(zip(*[iter(a)]*2))))


def rev5(a):
    n = len(a) / 2
    fmt = '%dh' % n
    return struct.pack(fmt, *reversed(struct.unpack(fmt, a)))

def rev6(a):
    return "".join([a[x:x+2] for x in range(0,len(a),2)][::-1])


print "Greg Hewgill %f" %timeit.Timer("rev2(string)", "from __main__ import rev2, string").timeit(100000)
print "gnibbler %f" %timeit.Timer("rev3(string)", "from __main__ import rev3, string").timeit(100000)
print "gnibbler second %f" %timeit.Timer("rev4(string)", "from __main__ import rev4, string").timeit(100000)
print "Alok %f" %timeit.Timer("rev5(string)", "from __main__ import rev5, struct, string").timeit(100000)
print "elliot42 %f" %timeit.Timer("rev6(string)", "from __main__ import rev6, struct, string").timeit(100000)
print "me %f" %timeit.Timer("rev(string)", "from __main__ import rev, string").timeit(100000)

results for string = "ABCDEFGH":

Greg Hewgill 0.853000
gnibbler 0.428000
gnibbler second 0.707000
Alok 0.763000
elliot42 0.237000
me 0.200000

results for string = "ABCDEFGH"*5:

Greg Hewgill 2.246000
gnibbler 0.811000
gnibbler second 1.205000
Alok 0.972000
elliot42 0.594000
me 0.584000

results for string = "ABCDEFGH"*10:

Greg Hewgill 2.058000
gnibbler 1.178000
gnibbler second 1.926000
Alok 1.210000
elliot42 0.935000
me 1.082000

results for string = "ABCDEFGH"*100:

Greg Hewgill 9.762000
gnibbler 9.134000
gnibbler second 14.782000
Alok 5.775000
elliot42 7.351000
me 18.140000

*Sorry @Lacrymology could not make your's work!

10 Comments

@elliot42: Thats because you were the fastest! :P
You should use string without it being surrounded by single quotes (and import the name string in your setup statement as well). Otherwise you're checking the time for running on the constant string 'string'. BTW, my method is the fastest for long strings (~50 characters or more on my computer). But I am not sure if I will write the function this way if it was production code :-).
Interesting how they scale. Did *1 and *100 tests with my own variant: def rev7(a): a=array.array('H',a) a.reverse() return a.tostring() Comparison to the fastest contenders: *1 Trufa 0.437, Yann 0.223. *100 Alok 5.19, Yann 2.38.
@YannVernier: Interesting indeed! I'll add as soon as I have some time you answer.
@Yann: nice. You should post that as an answer.
|
10
>>> import array
>>> s="abcdef"
>>> a=array.array('H',s)
>>> a.byteswap()
>>> a.tostring()
'badcfe'

Finish up by using a.reverse() instead of a.byteswap() if you wanted to swap element order rather than byte order.

I took the liberty of editing Trufa's benchmark script a bit. The modified script generated a graphical plot showing approximately linear scaling for all functions.

4 Comments

Neat module find! Wasn't familiar with that one.
This answer appears to be the most Pythonic of the bunch, the fastest, and most consistent. It should have the green check!
One more thing: change a.tostring() to a.tobytes(). The method in your answer is deprecated according to the documentation: docs.python.org/3/library/array.html And, change the string to a byte string. Python 3 array doesn't work with str and Python 2 thankfully dies in a fire in about 6 months.
Python 3.2, which has the documentation note that tostring is deprecated (yet still works in 3.7), had been released for mere weeks when this answer was written. The b prefix for byte literals wasn't available in e.g. the Python version in CentOS at the time. Forward porting this isn't obscure (it's one of the most well known changes in Python 3) and isn't relevant to the core question; I don't feel it needs this edit nor vitriol. tobytes is not available in Python 2.7, so it would specifically break compatibility.
4
st = "ABCDEFGH"
"".join([st[x:x+2] for x in range(0,len(st),2)][::-1])

EDIT: Curses, apparently 27 minutes slower than another poster. But I like the reverse slice notation better.

Some more information on the reverse slice here: "".join(reversed(val)) vs val[::-1]...which is pythonic?

1 Comment

Seems the most pythonic to me too, and fast. List slicing is very well optimised, although I rarely see it used in place of reverse() =/.
4

Here is a general form. The size of the grouping can easily be changed to a different number of characters at a time. The string length should be an exact multiple of the grouping size

>>> "".join(map("".join, reversed(zip(*[iter("ABCDEFGH")]*2))))
'GHEFCDAB'

(this is Python 2, it won't work in 3)

5 Comments

Love use of zip & map. I'm not familiar with this asterisk notation, could you explain pls.
Could you explain this solution a little further? As in what is going on?
Curiously obscure code. From the inside out: an iterator is created for the data, then duplicated as both arguments to zip (*2 to duplicate, * to apply list as arguments); effectively causing zip to pair up adjacent items (in order, but I'm not sure that's meant to be guaranteed) as it's the same iterator. The list of pairs is then reversed, and both pairs and the list of pairs are joined into a single string.
@Yann, the zip(*[iter()]*n) for grouping an iterator into chunks appears numerous times on SO already
3

You can use this, but don't tell anyone I wrote this code :-)

import struct

def pair_reverse(s):
    n = len(s) / 2
    fmt = '%dh' % n
    return struct.pack(fmt, *reversed(struct.unpack(fmt, s)))

pair_reverse('ABCDEFGH')

Comments

2

My friend Rob pointed out a beautiful recursive solution:

def f(s):
    return "" if not s else f(s[2:]) + s[:2]

Comments

0

just a shot

st = "ABCDEFGH"
s = [st[2*n:2*n+1] for n in range(len(st)/2)]
return s[::-1].join('')

this assumes that len(st) is even, otherwise change that to range(len(st)/2+1) and I'm even sure there's a better way to do that split into twos.

If your python complains about s[::-1] you can use reversed(s)

Comments

0

And yet another way:

a = "ABCDEFGH"
new = ""

for x in range(-1, -len(a), -2):
    new += a[x-1] + a[x]

print new

Comments

0

This looks like homework. So here's a very unorthodox approach that you might find interesting:

>>> s = "ABCDEFGH"
>>> ''.join([s[::2][::-1][i]+s[::-2][i] for i in range(len(s[::2]))])
'GHEFCDAB'

Good Luck!

Comments

0

and another gets on...

>>> rev = "ABCDEFGH"[::-1]
>>> ''.join([''.join(el) for el in zip(rev[1::2], rev[0::2])])
'GHEFCDAB'

Comments

0

I like this solution the most as it's the simplest and the neatest:

import struct
hex = struct.pack('<I', 0x41424344) #ABCD
print(hex) # BCDA

Comments

0

Here is a function based on the best, fastest, and most Pythonic answer above and in current Python 3 syntax:

def reverse_hex(hex_string):
    if isinstance(hex_string, str):
        input_is_string = True
        hex_string = hex_string.encode()
    a = array.array('H', hex_string)
    a.reverse()
    output = a.tobytes()
    if input_is_string:
        return output.decode()
    else:
        return output

Comments

Your Answer

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.