Перевернуть строку в Python по два символа за раз (сетевой порядок байтов)

Скажем, у вас есть эта строка:

ABCDEFGH

И вы хотите изменить его так, чтобы он стал:

GHEFCDAB

Что было бы наиболее эффективным/питоновским решением? Я пробовал несколько разных вещей, но все они выглядят ужасно...

Заранее спасибо!

Обновление:

Если кому-то интересно, это не домашнее задание. У меня был скрипт, который обрабатывал данные из сетевого захвата и возвращал их в виде строки шестнадцатеричных байтов. Проблема заключалась в том, что данные все еще находились в сетевом порядке. Из-за того, как было написано приложение, я не хотел возвращаться и пытаться использовать, скажем, socket.htons, я просто хотел изменить строку.

К сожалению, мои попытки казались настолько отвратительными, что я знал, что должен быть лучший способ (более питоническое решение) - отсюда и мой вопрос здесь.


person PeterM    schedule 03.05.2011    source источник
comment
Можем ли мы увидеть, что вы пробовали, возможно, мы сможем помочь улучшить это.   -  person Trufa    schedule 03.05.2011
comment
Боюсь, у меня его нет под рукой (он на работе), но он действительно отвратительный. На данный момент я использую очень странную конструкцию цикла. Я был бы рад простому решению заменить мой беспорядок :)   -  person PeterM    schedule 03.05.2011
comment
Каков правильный результат, если ввод содержит нечетное количество символов?   -  person Greg Hewgill    schedule 03.05.2011
comment
Вход никогда не будет содержать нечетное число, поскольку они представляют собой последовательности байтов.   -  person PeterM    schedule 03.05.2011
comment
Кроме того, нет, это не домашнее задание.   -  person PeterM    schedule 03.05.2011
comment
@jterrace это то, что я делаю, чтобы изменить шестнадцатеричный код RGB на BGR!   -  person TankorSmash    schedule 12.07.2012


Ответы (14)


Краткий способ сделать это:

"".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']

затем инвертировать это и, наконец, объединить результат вместе.

person Greg Hewgill    schedule 03.05.2011
comment
лол, я потратил минуту, написав около 12 строк, потом обновил страницу и увидел твою. - person a sandwhich; 03.05.2011

Много интересных способов сделать это

>>> s="ABCDEFGH"
>>> "".join(map(str.__add__, s[-2::-2] ,s[-1::-2]))
'GHEFCDAB'
person John La Rooy    schedule 03.05.2011
comment
оххх мило. Да, как он говорит, много забавных способов сделать это - person Lacrymology; 03.05.2011
comment
+1. Этот довольно аккуратный, так как он разбивается и переворачивается за один раз и не использует длину строки. - person Macke; 05.05.2011

Если кому-то интересно, это время для всех* ответов.

РЕДАКТИРОВАТЬ (в первый раз ошибся):

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)

результаты для string = "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 не смогла сделать вашу работу!

person Trufa    schedule 03.05.2011
comment
@elliot42: Это потому что ты был самым быстрым! :П - person Trufa; 03.05.2011
comment
Вы должны использовать string, не заключая его в одинарные кавычки (а также импортировать имя string в оператор установки). В противном случае вы проверяете время работы по константной строке 'string'. Кстати, мой метод является самым быстрым для длинных строк (~ 50 символов или более на моем компьютере). Но я не уверен, буду ли я писать функцию таким образом, если это будет производственный код :-). - person Alok Singhal; 03.05.2011
comment
Интересно, как они масштабируются. Провел тесты *1 и *100 с моим собственным вариантом: def rev7(a): a=array.array('H',a) a.reverse() return a.tostring() Сравнение с самыми быстрыми соперниками: *1 Trufa 0,437, Янн 0,223. *100 Алок 5.19, Янн 2.38. - person Yann Vernier; 03.05.2011
comment
@YannVernier: Действительно интересно! Я добавлю, как только у меня будет немного времени, чтобы вы ответили. - person Trufa; 03.05.2011
comment
@ Янн: хорошо. Вы должны опубликовать это как ответ. - person Alok Singhal; 04.05.2011
comment
@Alok: он сделал? - person Trufa; 04.05.2011
comment
ой, надо было раньше заметить. - person Alok Singhal; 04.05.2011
comment
Ради интереса, на моей машине также была точка (*3), где выделялась версия elliot42; но это все равно было elliot42 0,844, Yann 0,274. - person Yann Vernier; 04.05.2011
comment
@YannVernier: по какой-то причине я не смог запустить его внутри функции (хотя у меня было мало времени), я скоро посмотрю на это, извините :) - person Trufa; 04.05.2011
comment
Не стесняйтесь использовать версию, которую я указал в своем ответе. Что касается ненужной оптимизации, то from array import array может еще больше сократить количество запросов к пространству имен, сделав его самым быстрым и для размера 0. - person Yann Vernier; 04.05.2011

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

В завершение используйте a.reverse() вместо a.byteswap(), если вы хотите поменять местами порядок элементов, а не порядок байтов.

Я позволил себе немного отредактировать тестовый скрипт Trufa. модифицированный скрипт создал файл графический график, показывающий примерно линейное масштабирование для всех функций.

person Yann Vernier    schedule 03.05.2011
comment
Аккуратная находка модуля! Не был знаком с таким. - person elliot42; 05.05.2011
comment
Этот ответ кажется самым питоническим из всех, самым быстрым и последовательным. У него должна быть зеленая галочка! - person Utkonos; 07.07.2019
comment
Еще одна вещь: измените a.tostring() на a.tobytes(). Метод в вашем ответе устарел в соответствии с документацией: docs.python.org/3/ library/array.html И измените строку на строку байтов. Python 3 array не работает с str, а Python 2, к счастью, погибает в огне примерно через 6 месяцев. - person Utkonos; 07.07.2019
comment
Python 3.2, в документации которого указано, что tostring устарел (но все еще работает в 3.7), был выпущен всего за несколько недель, когда был написан этот ответ. Префикс b для байтовых литералов был недоступен, например. версия Python в CentOS в то время. Перенос вперед это не неясно (это одно из самых известных изменений в Python 3) и не имеет отношения к основному вопросу; Я не чувствую, что это нуждается в этом правке или купоросе. tobytes недоступен в Python 2.7, поэтому он явно нарушил бы совместимость. - person Yann Vernier; 09.07.2019

st = "ABCDEFGH"
"".join([st[x:x+2] for x in range(0,len(st),2)][::-1])

РЕДАКТИРОВАТЬ: Проклятия, по-видимому, на 27 минут медленнее, чем у другого плаката. Но мне больше нравится запись с обратным срезом.

Еще немного информации об обратном срезе здесь: val)) vs val[::-1]... что такое pythonic?

person elliot42    schedule 03.05.2011
comment
Мне тоже кажется самым питоническим и быстрым. Нарезка списка очень хорошо оптимизирована, хотя я редко вижу, чтобы она использовалась вместо reverse() =/. - person TyrantWave; 03.05.2011

Вот общая форма. Размер группы можно легко изменить на другое количество символов за раз. Длина строки должна быть кратна размеру группы.

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

(это Python 2, он не будет работать в 3)

person John La Rooy    schedule 03.05.2011
comment
Люблю использовать почтовый индекс и карту. Я не знаком с этой нотацией звездочки, не могли бы вы объяснить, пожалуйста. - person laher; 03.05.2011
comment
@amir75 docs.python.org/tutorial/ - person John La Rooy; 03.05.2011
comment
Не могли бы вы объяснить это решение немного подробнее? Как в том, что происходит? - person Senthil Kumaran; 03.05.2011
comment
Удивительно непонятный код. Изнутри наружу: для данных создается итератор, который затем дублируется как оба аргумента в zip (*2 для дублирования, * для применения списка в качестве аргументов); эффективно заставляя zip соединять соседние элементы (по порядку, но я не уверен, что это должно быть гарантировано), поскольку это один и тот же итератор. Затем список пар переворачивается, и обе пары и список пар объединяются в одну строку. - person Yann Vernier; 03.05.2011
comment
@Yann, zip(*[iter()]*n) для группировки итератора в куски уже много раз появляется на SO - person John La Rooy; 04.05.2011

Вы можете использовать это, но никому не говорите, что я написал этот код :-)

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')
person Alok Singhal    schedule 03.05.2011

Мой друг Rob указал на красивое рекурсивное решение:

def f(s):
    return "" if not s else f(s[2:]) + s[:2]
person elliot42    schedule 04.05.2011

просто выстрел

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)

person Lacrymology    schedule 03.05.2011

И еще способ:

a = "ABCDEFGH"
new = ""

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

print new
person Trufa    schedule 03.05.2011

Это похоже на домашнее задание. Итак, вот очень неортодоксальный подход, который может вас заинтересовать:

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

Удачи!

person inspectorG4dget    schedule 03.05.2011

а другой лезет...

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

Мне больше всего нравится это решение, так как оно самое простое и аккуратное:

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

Вот функция, основанная на лучшем, самом быстром и наиболее Pythonic ответе выше и в текущем синтаксисе Python 3:

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
person Utkonos    schedule 07.07.2019