В Python нет встроенной функции reverse
для объекта str
. Как лучше всего реализовать этот метод?
Если ответ будет очень кратким, пожалуйста, расскажите о его эффективности. Например, преобразуется ли объект str
в другой объект и т.д.
Как насчет:
>>> 'hello world'[::-1]
'dlrow olleh'
Это синтаксис extended slice. Он работает по принципу [begin:end:step]
- оставляя begin и end без внимания и указывая шаг -1, он переворачивает строку.
@Paolo's[::-1]
- самый быстрый; более медленный подход (возможно, более читабельный, но это спорно) - ''.join(reversed(s))
.
Какой лучший способ реализовать обратную функцию для строк?
Мой собственный опыт в этом вопросе академический. Однако, если вы профессионал, ищущий быстрый ответ, используйте фрагмент, который шагает по -1
:
>>> 'a string'[::-1]
'gnirts a'
или более читаемо (но медленнее из-за поиска имени метода и того факта, что объединение формирует список при предоставлении итератора), str.join
:
>>> ''.join(reversed('a string'))
'gnirts a'
или для удобочитаемости и повторного использования, поместите срез в функцию
def reversed_string(a_string):
return a_string[::-1]
а потом:
>>> reversed_string('a_string')
'gnirts_a'
Если вы заинтересованы в академической экспозиции, пожалуйста, продолжайте читать.
В объекте Python str нет встроенной обратной функции.
Вот пара вещей о строках Python, которые вы должны знать:
В Python струны неизменны . Изменение строки не изменяет строку. Это создает новый.
Струны нарезаются. Нажатие строки дает вам новую строку из одной точки в строке, назад или вперед, в другую точку с заданными приращениями. Они берут нотацию среза или объект среза в нижнем индексе:
string [subscript]
Нижний индекс создает срез, включая двоеточие в фигурных скобках:
string[start:stop:step]
Чтобы создать срез вне скобок, вам нужно создать объект среза:
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]
Но как мы можем сделать это более читабельным и понятным для кого-то, кто менее знаком с ломтиками или намерением оригинального автора? Давайте создадим объект среза за пределами обозначения индекса, дадим ему описательное имя и передадим его в обозначение индекса.
start = stop = None
step = -1
reverse_slice = slice(start, stop, step)
'foo'[reverse_slice]
Чтобы реально реализовать это как функцию, я думаю, что это достаточно семантически ясно, чтобы просто использовать описательное имя:
def reversed_string(a_string):
return a_string[::-1]
И использование просто:
reversed_string('foo')
Если у вас есть инструктор, они, вероятно, хотят, чтобы вы начали с пустой строки и создали новую строку из старой. Вы можете сделать это с помощью чистого синтаксиса и литералов, используя цикл while:
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
Это теоретически плохо, потому что, помните, струны неизменны - поэтому каждый раз, когда кажется, что вы добавляете символ в свою new_string
, он теоретически создает новую строку каждый раз! Тем не менее, CPython знает, как оптимизировать это в определенных случаях, одним из которых является этот тривиальный случай.
Теоретически лучше собрать ваши подстроки в списке и присоединиться к ним позже:
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)
Однако, как мы увидим в приведенных ниже таймингах для CPython, это на самом деле занимает больше времени, поскольку CPython может оптимизировать конкатенацию строк.
Вот тайминги:
>>> 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 оптимизирует конкатенацию строк, тогда как другие реализации не могут:
... не полагайтесь на эффективную реализацию CPython встроенной конкатенации строк для операторов в форме a + = b или a + b . Эта оптимизация хрупка даже в CPython (она работает только для некоторых типов) и вообще не присутствует в реализациях, которые не используют рефонтинг. В чувствительных к производительности частях библиотеки вместо этого следует использовать форму '' .join (). Это обеспечит конкатенацию в линейное время для различных реализаций.
### Пример
### example01 -------------------
mystring = 'coup_ate_grouping'
backwards = mystring[::-1]
print backwards
### ... or even ...
mystring = 'coup_ate_grouping'[::-1]
print mystring
### result01 -------------------
'''
gnipuorg_eta_puoc
'''
Этот ответ предоставлен для решения следующей проблемы от @odigity:
Ух ты. Сначала я был в ужасе от решения, предложенного Паоло, но это отошел на второй план от ужаса, который я почувствовал, прочитав первый комментарий: «Это очень питонично. Хорошая работа!"Я так обеспокоен, что такой яркое сообщество думает, что использует такие загадочные методы для чего-то такого Базовая это хорошая идея. Почему это не просто s.reverse ()?
string.reverse ()
string.reverse ()
, чтобы избежать нотации срезов.print 'coup_ate_grouping' [-4:] ## = > 'пинг'
print 'coup_ate_grouping' [-4: -1] ## = > 'булавка'
print 'coup_ate_grouping' [-1] ## = > 'Г'
[-1]
могут сбить некоторых разработчиковУ Python есть особые обстоятельства, о которых следует знать: строка является типом iterable.
Одним из обоснований исключения метода string.reverse ()
является стимулирование разработчиков python использовать силу этого особого обстоятельства.
В упрощенном выражении это просто означает, что каждый отдельный символ в строке может легко управляться как часть последовательного расположения элементов, как массивы в других языках программирования.
Чтобы понять, как это работает, обзор example02 может дать хороший обзор.
### example02 -------------------
## start (with positive integers)
print 'coup_ate_grouping'[0] ## => 'c'
print 'coup_ate_grouping'[1] ## => 'o'
print 'coup_ate_grouping'[2] ## => 'u'
## start (with negative integers)
print 'coup_ate_grouping'[-1] ## => 'g'
print 'coup_ate_grouping'[-2] ## => 'n'
print 'coup_ate_grouping'[-3] ## => 'i'
## start:end
print 'coup_ate_grouping'[0:4] ## => 'coup'
print 'coup_ate_grouping'[4:8] ## => '_ate'
print 'coup_ate_grouping'[8:12] ## => '_gro'
## start:end
print 'coup_ate_grouping'[-4:] ## => 'ping' (counter-intuitive)
print 'coup_ate_grouping'[-4:-1] ## => 'pin'
print 'coup_ate_grouping'[-4:-2] ## => 'pi'
print 'coup_ate_grouping'[-4:-3] ## => 'p'
print 'coup_ate_grouping'[-4:-4] ## => ''
print 'coup_ate_grouping'[0:-1] ## => 'coup_ate_groupin'
print 'coup_ate_grouping'[0:] ## => 'coup_ate_grouping' (counter-intuitive)
## start:end:step (or start:end:stride)
print 'coup_ate_grouping'[-1::1] ## => 'g'
print 'coup_ate_grouping'[-1::-1] ## => 'gnipuorg_eta_puoc'
## combinations
print 'coup_ate_grouping'[-1::-1][-4:] ## => 'puoc'
Когнитивная нагрузка, связанная с пониманием того, как в python работает запись срезов, действительно может быть слишком большой для некоторых пользователей и разработчиков, которые не хотят вкладывать много времени в изучение язык.
Тем не менее, как только основные принципы поняты, сила этого подхода над методами манипулирования фиксированными струнами может быть весьма благоприятной.
Для тех, кто думает иначе, существуют альтернативные подходы, такие как лямбда-функции, итераторы или простые одноразовые объявления функций.
При желании разработчик может реализовать свой собственный метод string.reverse (), однако полезно понять обоснование этого аспекта питона.
Менее озадачивающий способ взглянуть на это будет:
string = 'happy'
print(string)
«Счастливый»
string_reversed = string[-1::-1]
print(string_reversed)
«Иппа»
На английском языке [-1 :: - 1] читается как:
«Начиная с -1, пройдите весь путь, делая шаги -1»
Существующие ответы верны только в том случае, если игнорируются модификаторы Unicode / кластеры графемы. Я разберусь с этим позже, но сначала взгляну на скорость некоторых алгоритмов реверсирования
list_comprehension : 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
list_comprehension : 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
Вы можете видеть, что время для понимания списка (reversed = string [:: -1]
) во всех случаях является самым низким (даже после исправления моей опечатки).
Если вы действительно хотите изменить строку в здравом смысле, это НАМНОГО сложнее. Например, возьмите следующую строку (коричневый палец, указывающий влево, [желтый палец, указывающий вверх](https:// emojipedia.org/white-up-pointing-backhand-index/)). Это две графемы, но 3 кодовых точки Unicode. Дополнительным является модификатор кожи.
example = "👈🏾👆"
Но если вы измените его любым из указанных методов, вы получите коричневый палец, указывающий вверх, желтый палец, указывающий влево. Причина этого заключается в том, что «коричневый» модификатор цвета все еще находится посередине и применяется ко всему, что есть до него. Итак, у нас есть
а также
original: LMU
reversed: UML (above solutions)
reversed: ULM (correct reversal)
Unicode Grapheme Clusters немного сложнее, чем просто кодовые точки модификатора. К счастью, есть библиотека для обработки graphemes:
>>> import grapheme
>>> g = grapheme.graphemes("👈🏾👆")
>>> list(g)
['👈🏾', '👆']
и, следовательно, правильный ответ будет
def reverse_graphemes(string):
g = list(grapheme.graphemes(string))
return ''.join(g[::-1])
который также является безусловно самым медленным:
list_comprehension : 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 python
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 = [(list_comprehension, 'list_comprehension', 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 list_comprehension(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()
def rev_string(s):
return s[::-1]
def rev_string(s):
return ''.join(reversed(s))
def rev_string(s):
if len(s) == 1:
return s
return s[-1] + rev_string(s[:-1])
Это также интересный способ:
def reverse_words_1(s):
rev = ''
for i in range(len(s)):
j = ~i # equivalent to j = -(i + 1)
rev += s[j]
return rev
или аналогичный:
def reverse_words_2(s):
rev = ''
for i in reversed(range(len(s)):
rev += s[i]
return rev
Еще один более «экзотический» способ с использованием byterarray, который поддерживает .reverse ()
b = bytearray('Reverse this!', 'UTF-8')
b.reverse()
b.decode('UTF-8')
будет производить:
'!siht esreveR'
Обращение строки в python без использования reversed() или [::-1]
def reverse(test):
n = len(test)
x=""
for i in range(n-1,-1,-1):
x += test[i]
return x
Здесь нет фантазии:
def reverse(text):
r_text = ''
index = len(text) - 1
while index >= 0:
r_text += text[index] #string canbe concatenated
index -= 1
return r_text
print reverse("hello, world!")
Все вышеперечисленные решения идеальны, но если мы попытаемся изменить строку с помощью цикла для python, это станет немного сложно, поэтому мы можем изменить строку, используя цикл
string ="hello,world"
for i in range(-1,-len(string)-1,-1):
print (string[i],end=(" "))
Я надеюсь, что этот будет полезен для кого-то.
def reverse_string(string):
length = len(string)
temp = ''
for i in range(length):
temp += string[length - i - 1]
return temp
print(reverse_string('foo')) #prints "oof"
Это работает путем циклирования строки и присвоения ее значений в обратном порядке другой строке.
Есть много способов изменить строку, но я также создал еще один просто для удовольствия. Я думаю, что этот подход не так уж и плох.
def reverse(_str):
list_char = list(_str) # Create a hypothetical list. because string is immutable
for i in range(len(list_char)/2): # just t(n/2) to reverse a big string
list_char[i], list_char[-i - 1] = list_char[-i - 1], list_char[i]
return ''.join(list_char)
print(reverse("Ehsan"))
Этот класс использует магические функции питона для изменения строки:
class Reverse(object):
""" Builds a reverse method using magic methods """
def __init__(self, data):
self.data = data
self.index = len(data)
def __iter__(self):
return self
def __next__(self):
if self.index == 0:
raise StopIteration
self.index = self.index - 1
return self.data[self.index]
REV_INSTANCE = Reverse('hello world')
iter(REV_INSTANCE)
rev_str = ''
for char in REV_INSTANCE:
rev_str += char
print(rev_str)
dlrow olleh
Вот один без [:: -1]
илиreversed
(для учебных целей):
def reverse(text):
new_string = []
n = len(text)
while (n > 0):
new_string.append(text[n-1])
n -= 1
return ''.join(new_string)
print reverse("abcd")
Вы можете использовать + =
, чтобы объединить строки, ноjoin ()
быстрее.