Reverse a string in Python two characters at a time (Network byte order)
Question:
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.
Answers:
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)
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.
Lots of fun ways to do this
>>> s="ABCDEFGH"
>>> "".join(map(str.__add__, s[-2::-2] ,s[-1::-2]))
'GHEFCDAB'
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)
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')
And yet another way:
a = "ABCDEFGH"
new = ""
for x in range(-1, -len(a), -2):
new += a[x-1] + a[x]
print new
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?
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!
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!
>>> 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.
My friend Rob pointed out a beautiful recursive solution:
def f(s):
return "" if not s else f(s[2:]) + s[:2]
and another gets on…
>>> rev = "ABCDEFGH"[::-1]
>>> ''.join([''.join(el) for el in zip(rev[1::2], rev[0::2])])
'GHEFCDAB'
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
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
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.
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)
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.
Lots of fun ways to do this
>>> s="ABCDEFGH"
>>> "".join(map(str.__add__, s[-2::-2] ,s[-1::-2]))
'GHEFCDAB'
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)
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')
And yet another way:
a = "ABCDEFGH"
new = ""
for x in range(-1, -len(a), -2):
new += a[x-1] + a[x]
print new
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?
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!
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!
>>> 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.
My friend Rob pointed out a beautiful recursive solution:
def f(s):
return "" if not s else f(s[2:]) + s[:2]
and another gets on…
>>> rev = "ABCDEFGH"[::-1]
>>> ''.join([''.join(el) for el in zip(rev[1::2], rev[0::2])])
'GHEFCDAB'
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
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