Как мне обратить строку вспять в Python?
Нет встроенного reverse
метода для str
объекта Python. Как я могу обратить строку вспять?
Переведено автоматически
Ответ 1
Используя нарезку:
>>> 'hello world'[::-1]
'dlrow olleh'
Нотация среза имеет вид [start:stop:step]
. В этом случае мы опускаем позиции start
и stop
, поскольку нам нужна строка целиком. Мы также используем step = -1
, что означает "повторный шаг справа налево на 1 символ".
Ответ 2
У @Paolo s[::-1]
самый быстрый; более медленный подход (возможно, более читаемый, но это спорно) ''.join(reversed(s))
.
Ответ 3
Каков наилучший способ реализации функции обратного преобразования для строк?
Мой собственный опыт работы с этим вопросом академический. Однако, если вы профессионал, ищущий быстрый ответ, используйте фрагмент, пошагово описывающий-1
:
>>> 'a string'[::-1]
'gnirts a'
или более читабельно (но медленнее из-за поиска имени метода и того факта, что join формирует список при задании итератора), str.join
:
>>> ''.join(reversed('a string'))
'gnirts a'
или для удобства чтения и повторного использования поместите фрагмент в функцию
def reversed_string(a_string):
return a_string[::-1]
а затем:
>>> reversed_string('a_string')
'gnirts_a'
Более подробное объяснение
Если вам интересно академическое изложение, пожалуйста, продолжайте читать.
В объекте str Python нет встроенной функции обратного преобразования.
Вот пара вещей о строках Python, которые вы должны знать:
В Python строки неизменяемы. Изменение строки не изменяет саму строку. Оно создает новую.
Строки можно разрезать. При разрезании строки получается новая строка из одной точки строки, назад или вперед, в другую точку с заданными приращениями. Они принимают нотацию среза или объект среза в нижнем индексе:
string[subscript]
Нижний индекс создает фрагмент, включая двоеточие в фигурные скобки:
string[start:stop:step]
Чтобы создать фрагмент вне фигурных скобок, вам нужно создать объект slice:
slice_obj = slice(start, stop, step)
string[slice_obj]
Понятный подход:
Хотя ''.join(reversed('foo'))
это доступно для чтения, для этого требуется вызвать строковый метод str.join
для другой вызываемой функции, что может быть относительно медленным. Давайте поместим это в функцию - мы вернемся к этому:
def reverse_string_readable_answer(string):
return ''.join(reversed(string))
Наиболее эффективный подход:
Гораздо быстрее использовать обратный фрагмент:
'foo'[::-1]
Но как мы можем сделать это более читаемым и понятным для кого-то, кто менее знаком с фрагментами или намерениями автора оригинала? Давайте создадим объект slice вне подстрочной нотации, дадим ему описательное имя и передадим его подстрочной нотации.
start = stop = None
step = -1
reverse_slice = slice(start, stop, step)
'foo'[reverse_slice]
Implement as Function
To actually implement this as a function, I think it is semantically clear enough to simply use a descriptive name:
def reversed_string(a_string):
return a_string[::-1]
And usage is simply:
reversed_string('foo')
What your teacher probably wants:
If you have an instructor, they probably want you to start with an empty string, and build up a new string from the old one. You can do this with pure syntax and literals using a while loop:
def reverse_a_string_slowly(a_string):
new_string = ''
index = len(a_string)
while index:
index -= 1 # index = index - 1
new_string += a_string[index] # new_string = new_string + character
return new_string
This is theoretically bad because, remember, strings are immutable - so every time where it looks like you're appending a character onto your new_string
, it's theoretically creating a new string every time! However, CPython knows how to optimize this in certain cases, of which this trivial case is one.
Best Practice
Theoretically better is to collect your substrings in a list, and join them later:
def reverse_a_string_more_slowly(a_string):
new_strings = []
index = len(a_string)
while index:
index -= 1
new_strings.append(a_string[index])
return ''.join(new_strings)
However, as we will see in the timings below for CPython, this actually takes longer, because CPython can optimize the string concatenation.
Timings
Here are the timings:
>>> a_string = 'amanaplanacanalpanama' * 10
>>> min(timeit.repeat(lambda: reverse_string_readable_answer(a_string)))
10.38789987564087
>>> min(timeit.repeat(lambda: reversed_string(a_string)))
0.6622700691223145
>>> min(timeit.repeat(lambda: reverse_a_string_slowly(a_string)))
25.756799936294556
>>> min(timeit.repeat(lambda: reverse_a_string_more_slowly(a_string)))
38.73570013046265
CPython optimizes string concatenation, whereas other implementations may not:
... do not rely on CPython's efficient implementation of in-place string concatenation for statements in the form a += b or a = a + b . This optimization is fragile even in CPython (it only works for some types) and isn't present at all in implementations that don't use refcounting. In performance sensitive parts of the library, the ''.join() form should be used instead. This will ensure that concatenation occurs in linear time across various implementations.
Ответ 4
This answer is a bit longer and contains 3 sections: Benchmarks of existing solutions, why most solutions here are wrong, my solution.
The existing answers are only correct if Unicode Modifiers / grapheme clusters are ignored. I'll deal with that later, but first have a look at the speed of some reversal algorithms:
NOTE: I've what I called list_comprehension
should be called slicing
slicing : min: 0.6μs, mean: 0.6μs, max: 2.2μs
reverse_func : min: 1.9μs, mean: 2.0μs, max: 7.9μs
reverse_reduce : min: 5.7μs, mean: 5.9μs, max: 10.2μs
reverse_loop : min: 3.0μs, mean: 3.1μs, max: 6.8μs
slicing : min: 4.2μs, mean: 4.5μs, max: 31.7μs
reverse_func : min: 75.4μs, mean: 76.6μs, max: 109.5μs
reverse_reduce : min: 749.2μs, mean: 882.4μs, max: 2310.4μs
reverse_loop : min: 469.7μs, mean: 577.2μs, max: 1227.6μs
You can see that the time for the slicing (reversed = string[::-1]
) is in all cases by far the lowest (even after fixing my typo).
Обращение строки вспять
Если вы действительно хотите обратить строку вспять в обычном смысле, это НАМНОГО сложнее. Для примера возьмем следующую строку (коричневый палец, указывающий влево, желтый палец, указывающий вверх). Это две графемы, но 3 кодовые точки unicode. Дополнительный - это модификатор обложки.
example = "👈🏾👆"
Но если вы перевернете ее любым из приведенных методов, вы получите коричневый палец, указывающий вверх, желтый палец, указывающий влево. Причина этого в том, что модификатор цвета "brown" по-прежнему находится посередине и применяется ко всему, что находится перед ним. Итак, мы имеем
- U: палец, указывающий вверх
- M: модификатор brown
- L: палец указывает влево
и
original: LMU 👈🏾👆
reversed: UML (above solutions) ☝🏾👈
reversed: ULM (correct reversal) 👆👈🏾
Кластеры графем в Юникоде немного сложнее, чем просто кодовые точки модификатора. К счастью, существует библиотека для обработки графем:
>>> import grapheme
>>> g = grapheme.graphemes("👈🏾👆")
>>> list(g)
['👈🏾', '👆']
и, следовательно, правильным ответом будет
def reverse_graphemes(string):
g = list(grapheme.graphemes(string))
return ''.join(g[::-1])
который также, безусловно, самый медленный:
slicing : min: 0.5μs, mean: 0.5μs, max: 2.1μs
reverse_func : min: 68.9μs, mean: 70.3μs, max: 111.4μs
reverse_reduce : min: 742.7μs, mean: 810.1μs, max: 1821.9μs
reverse_loop : min: 513.7μs, mean: 552.6μs, max: 1125.8μs
reverse_graphemes : min: 3882.4μs, mean: 4130.9μs, max: 6416.2μs
Код
#!/usr/bin/env python3
import numpy as np
import random
import timeit
from functools import reduce
random.seed(0)
def main():
longstring = ''.join(random.choices("ABCDEFGHIJKLM", k=2000))
functions = [(slicing, 'slicing', longstring),
(reverse_func, 'reverse_func', longstring),
(reverse_reduce, 'reverse_reduce', longstring),
(reverse_loop, 'reverse_loop', longstring)
]
duration_list = {}
for func, name, params in functions:
durations = timeit.repeat(lambda: func(params), repeat=100, number=3)
duration_list[name] = list(np.array(durations) * 1000)
print('{func:<20}: '
'min: {min:5.1f}μs, mean: {mean:5.1f}μs, max: {max:6.1f}μs'
.format(func=name,
min=min(durations) * 10**6,
mean=np.mean(durations) * 10**6,
max=max(durations) * 10**6,
))
create_boxplot('Reversing a string of length {}'.format(len(longstring)),
duration_list)
def slicing(string):
return string[::-1]
def reverse_func(string):
return ''.join(reversed(string))
def reverse_reduce(string):
return reduce(lambda x, y: y + x, string)
def reverse_loop(string):
reversed_str = ""
for i in string:
reversed_str = i + reversed_str
return reversed_str
def create_boxplot(title, duration_list, showfliers=False):
import seaborn as sns
import matplotlib.pyplot as plt
import operator
plt.figure(num=None, figsize=(8, 4), dpi=300,
facecolor='w', edgecolor='k')
sns.set(style="whitegrid")
sorted_keys, sorted_vals = zip(*sorted(duration_list.items(),
key=operator.itemgetter(1)))
flierprops = dict(markerfacecolor='0.75', markersize=1,
linestyle='none')
ax = sns.boxplot(data=sorted_vals, width=.3, orient='h',
flierprops=flierprops,
showfliers=showfliers)
ax.set(xlabel="Time in ms", ylabel="")
plt.yticks(plt.yticks()[0], sorted_keys)
ax.set_title(title)
plt.tight_layout()
plt.savefig("output-string.png")
if __name__ == '__main__':
main()