在Python中每次两个字符反转字符串(网络字节顺序)

42

假设你有这个字符串:

ABCDEFGH

而您想要将其反转,使其变为:

GHEFCDAB
什么是最高效/Pythonic的解决方案?我已经尝试了几种不同的方法,但它们看起来都很糟糕...
提前感谢您!更新:如果有人感兴趣,这不是为了家庭作业。我有一个脚本可以处理来自网络捕获的数据并将其返回为十六进制字节的字符串。问题是数据仍然按网络顺序排序。由于应用程序的编写方式,我不想回过头来尝试使用socket.htons之类的函数,我只想反转字符串。
不幸的是,我的尝试看起来非常丑陋,我知道必须有更好的方法(更Pythonic的解决方案)-因此我在这里提出了我的问题。

2
你能让我们看看你尝试了什么吗?也许我们可以帮助你改进它。 - Trufa
很抱歉,我手头没有它(它在工作中),但它真的很丑陋。目前我正在使用一个非常奇怪的循环结构。如果有一个简单的解决方案来替换我的混乱,我会很高兴的。 - PeterM
如果输入包含奇数个字符,正确的结果是什么? - Greg Hewgill
1
输入永远不会包含奇数,因为它们是字节序列。 - PeterM
还有,这不是作业。 - PeterM
@jterrace 这就是我正在做的将RGB的十六进制转换为BGR的过程! - TankorSmash
14个回答

39

一个简洁的方式是:

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

这段代码的作用是将字符串首先拆分成一对一对的字符:

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

然后将其反转,最后将结果拼接在一起。


2
我花了一分钟写了大约12行代码,然后刷新页面看到你的。 - a sandwhich

15

有许多有趣的方法可以做到这一点

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

哦,很可爱。是的,正如他所说,有很多有趣的方法可以做到这一点。 - Lacrymology
+1. 这个很棒,因为它一次性完成了字符串的拆分和反转,并且不使用字符串的长度。 - Macke

14

如果有人感兴趣,这是所有答案的时间安排。

编辑(第一次理解错误):

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)

字符串"ABCDEFGH"的搜索结果:

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

字符串string = "ABCDEFGH"*5的结果为:

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

string = "ABCDEFGH"*10的结果:

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

string = "ABCDEFGH"*100的结果:

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

*抱歉 @Lacrymology,无法使你的工作正常运行!


@elliot42:那是因为你是最快的! :P - Trufa
有趣的是它们如何扩展。我用自己的变体进行了1和100测试:def rev7(a): a=array.array('H',a) a.reverse() return a.tostring()与最快的竞争者相比:1 Trufa 0.437,Yann 0.223。100 Alok 5.19,Yann 2.38。 - Yann Vernier
@YannVernier:确实很有趣!我一有时间就会添加你的答案。 - Trufa
糟糕,早该注意到了。 - Alok Singhal
仅仅是为了好玩,在我的机器上也有一个点(*3),在那个点上elliot42的版本突出了;但是它仍然是elliot42 0.844,Yann 0.274。 - Yann Vernier
显示剩余5条评论

10
>>> import array
>>> s="abcdef"
>>> a=array.array('H',s)
>>> a.byteswap()
>>> a.tostring()
'badcfe'

如果您想要交换元素顺序而不是字节顺序,则可以使用a.reverse(),而不是a.byteswap()来完成。

我稍微编辑了Trufa的基准测试脚本。修改后的脚本生成了一个图形绘图,显示了所有功能的近似线性扩展。


不错的模块发现!我之前不太熟悉这个。 - elliot42
这个答案似乎是最符合Python风格的,速度最快且最一致的。它应该有绿色的勾! - Utkonos
还有一件事:将 a.tostring() 更改为 a.tobytes()。根据文档,您回答中的方法已被弃用:https://docs.python.org/3/library/array.html 并且,将字符串更改为字节串。Python 3 array 不适用于 str,而 Python 2 将在约6个月后停止使用。 - Utkonos
Python 3.2在发布仅数周后,就已经有文档注明tostring已被弃用(但在3.7中仍然可用)。在CentOS中的Python版本中,例如没有字节文字的b前缀。将其向前移植并不难(这是Python 3中最为人知的变化之一),也与核心问题无关;我认为它不需要这种编辑或者愤怒。tobytes在Python 2.7中不可用,因此会特别破坏兼容性。 - Yann Vernier

4

这里是一个通用表单。分组的大小可以轻松地更改为每次不同数量的字符。字符串长度应该是分组大小的精确倍数。

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

(这是Python 2,不适用于3)


1
喜欢使用zip和map。我不熟悉这个星号符号,请你解释一下。 - laher
1
@amir75 http://docs.python.org/tutorial/controlflow.html#unpacking-argument-lists(注意:该链接可能需要科学上网才能访问) - John La Rooy
你能进一步解释一下这个解决方案吗?比如说,它是怎么运作的? - Senthil Kumaran
有趣而晦涩的代码。从内到外:为数据创建了一个迭代器,然后将其复制为zip的两个参数(2表示复制,表示将列表应用为参数);这有效地使zip将相邻的项目配对(按顺序,但我不确定是否保证)。然后反转了一下配对列表,并将配对和配对列表连接成单个字符串。 - Yann Vernier
@Yann,使用zip(*[iter()]*n)将迭代器分组成块已经在SO上出现了很多次。 - John La Rooy

4

对我来说,这似乎是最符合Python风格的方法,而且速度也很快。列表切片非常优化,尽管我很少看到它被用来替代reverse()函数 =/. - TyrantWave

3

你可以使用这个,但不要告诉任何人我写了这段代码 :-)

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')

2

我的朋友Rob提供了一个优美的递归解决方案:

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

0

只是一试

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

假设len(st)是偶数,否则将其更改为range(len(st)/2+1),我甚至确定有更好的方法将其分成两个。

如果你的Python抱怨s[::-1],你可以使用reversed(s)


0

还有另一种方式:

a = "ABCDEFGH"
new = ""

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

print new

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接