在Python中反转字符串

Python的str对象没有内置的reverse函数。实现此方法的最佳方法是什么?

如果回答非常简洁,请详细说明其效率。例如,str对象是否转换为不同的对象等。

1549497 次浏览

使用切片

>>> 'hello world'[::-1]'dlrow olleh'

切片表示法采用[start:stop:step]的形式。在这种情况下,我们省略了startstop的位置,因为我们想要整个字符串。我们还使用step = -1,这意味着“从右到左重复一步1个字符”。

@Paolo的s[::-1]是最快的;较慢的方法(可能更具可读性,但这是有争议的)是''.join(reversed(s))

在Python中反转一个字符串,不使用反向()或[::-1]

def reverse(test):n = len(test)x=""for i in range(n-1,-1,-1):x += test[i]return x

实现字符串反向函数的最佳方法是什么?

我自己对这个问题的经验是学术上的。然而,如果你是一个寻找快速答案的专业人士,请使用按-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字符串的一些你应该知道的事情:

  1. 在Python中,字符串是不可变的。更改字符串不会修改字符串。它会创建一个新字符串。

  2. 字符串是可切片的。切片字符串为您提供一个新的字符串,从字符串中的一点,向后或向前,到另一个点,通过给定的增量。它们采用切片符号或下标中的切片对象:

    string[subscript]

The subscript creates a slice by including a colon within the braces:

    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 = Nonestep = -1reverse_slice = slice(start, stop, step)'foo'[reverse_slice]

作为函数实现

要实际实现这个函数,我认为语义上足够清楚,只需使用描述性名称:

def reversed_string(a_string):return a_string[::-1]

用法很简单:

reversed_string('foo')

你的老师可能想要什么:

如果你有一个讲师,他们可能希望你从一个空字符串开始,并从旧字符串构建一个新字符串。您可以使用纯语法和文字来执行此操作:

def reverse_a_string_slowly(a_string):new_string = ''index = len(a_string)while index:index -= 1                    # index = index - 1new_string += a_string[index] # new_string = new_string + characterreturn new_string

这在理论上是不好的,因为,记住,字符串是不可变的-所以每次看起来你在new_string上附加一个字符,理论上每次都在创建一个新字符串!然而,CPython知道如何在某些情况下优化它,这个琐碎的案例就是其中之一。

最佳实践

理论上更好的做法是将你的子字符串收集到一个列表中,然后加入它们:

def reverse_a_string_more_slowly(a_string):new_strings = []index = len(a_string)while index:index -= 1new_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=a+b形式的语句的就地字符串连接的高效实现。即使在CPython中,这种优化也很脆弱(它只适用于某些类型),并且在不使用反计数的实现中根本不存在。在库的性能敏感部分,应该使用“. join()形式。这将确保跨各种实现的串联在线性时间内发生。

这是一个没有幻想的:

def reverse(text):r_text = ''index = len(text) - 1
while index >= 0:r_text += text[index] #string canbe concatenatedindex -= 1
return r_text
print reverse("hello, world!")
def reverse(input):return reduce(lambda x,y : y+x, input)

答案(太长别读)

示例

### example01 -------------------mystring  =   'coup_ate_grouping'backwards =   mystring[::-1]print(backwards)
### ... or even ...mystring  =   'coup_ate_grouping'[::-1]print(mystring)
### result01 -------------------'''gnipuorg_eta_puoc'''

详细答案

背景

提供此答案是为了解决@odigity的以下问题:

哇,我一开始被保罗提出的解决方案吓坏了,但是退居二线,我读了第一本书后感到恐惧评论:“这是非常pythonic。好工作!”我很不安,这样的一个聪明的社区认为用这种神秘的方法来做一些如此基础是个好主意。为什么不s.reverse ()?

问题

  • 背景
    • python2. x
    • python3. x
  • 场景:
    • 开发人员想要转换字符串
    • 转换是颠倒所有字符的顺序

解决方案

陷阱

  • 开发人员可能会期望类似于string.reverse()的东西
  • 本机惯用(又名“pythonic”)解决方案可能对新开发人员不易读
  • 开发人员可能会试图实现他或她自己的string.reverse()版本以避免切片表示法。
  • 切片符号的输出在某些情况下可能是反直觉的:
    • 例如,例子02
      • print 'coup_ate_grouping'[-4:] ## => 'ping'
      • 相比之下
      • print 'coup_ate_grouping'[-4:-1] ## => 'pin'
      • 相比之下
      • print 'coup_ate_grouping'[-1] ## => 'g'
    • [-1]上索引的不同结果可能会让一些开发人员望而却步

理由

Python有一个特殊的情况需要注意:字符串是迭代类型。

排除string.reverse()方法的一个理由是让python开发人员有动力利用这种特殊情况的力量。

简而言之,这意味着字符串中的每个单独字符都可以作为元素顺序排列的一部分轻松操作,就像其他编程语言中的数组一样。

要理解这是如何工作的,回顾示例02可以提供一个很好的概述。

实例02

### 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:endprint 'coup_ate_grouping'[0:4]    ## => 'coup'print 'coup_ate_grouping'[4:8]    ## => '_ate'print 'coup_ate_grouping'[8:12]   ## => '_gro'
## start:endprint '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'
## combinationsprint 'coup_ate_grouping'[-1::-1][-4:] ## => 'puoc'

结论

与了解切片表示法在python中的工作原理相关的认知负荷对于一些不希望在学习语言上投入太多时间的采用者和开发人员来说确实太多了。

然而,一旦理解了基本原理,这种方法相对于固定字符串操作方法的能力就会非常有利。

对于那些不这么认为的人来说,还有其他方法,例如lambda函数、迭代器或简单的一次性函数声明。

如果需要,开发人员可以实现自己的string.reverse()方法,但是了解Python这方面背后的基本原理是很好的。

另见

一个不那么令人困惑的方式来看待它:

string = 'happy'print(string)

“快乐”

string_reversed = string[-1::-1]print(string_reversed)

'yppah'

英语[-1::-1]读作:

“从-1开始,一直走下去,采取-1的步骤”

递归方法:

def reverse(s): return s[0] if len(s)==1 else s[len(s)-1] + reverse(s[0:len(s)-1])

例子:

print(reverse("Hello!"))    #!olleH

以下是我们如何使用for循环反转字符串:

string = "hello,world"for i in range(-1,-len(string)-1,-1):print (string[i], end=(" "))

1.使用切片表示法

def rev_string(s):return s[::-1]

2.使用反向()函数

def rev_string(s):return ''.join(reversed(s))

3.使用递归

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

另一种更“异国情调”的方式使用字节数组,它支持.反向()

b = bytearray('Reverse this!', 'UTF-8')b.reverse()b.decode('UTF-8')`

将产生:

'!siht esreveR'
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"

这是通过循环遍历一个字符串并将其值以相反的顺序分配给另一个字符串来实现的。

original = "string"
rev_index = original[::-1]rev_func = list(reversed(list(original))) #nsfw
print(original)print(rev_index)print(''.join(rev_func))

这个答案有点长,包含3个部分:现有解决方案的基准为什么大多数解决方案都是错误的我的解决方案

现有的答案只有在忽略Unicode修饰符/字位簇时才正确。我稍后会处理,但首先看看一些反转算法的速度:

输入图片描述

list_comprehension  : min:   0.6μs, mean:   0.6μs, max:    2.2μsreverse_func        : min:   1.9μs, mean:   2.0μs, max:    7.9μsreverse_reduce      : min:   5.7μs, mean:   5.9μs, max:   10.2μsreverse_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μsreverse_func        : min:  75.4μs, mean:  76.6μs, max:  109.5μsreverse_reduce      : min: 749.2μs, mean: 882.4μs, max: 2310.4μsreverse_loop        : min: 469.7μs, mean: 577.2μs, max: 1227.6μs

您可以看到,列表理解(reversed = string[::-1])的时间在所有情况下都是最低的(即使在修复了我的错字之后)。

串反转

如果你真的想反转常识中的字符串,那就要复杂得多。例如,以以下字符串(棕色手指指向左边黄色手指向上指)为例。这是两个图形,但有3个Unicode代码点。另外一个是皮肤改性剂

example = "👈🏾👆"

但是如果你用任何给定的方法颠倒它,你会得到棕色手指向上黄色手指指向左边。这样做的原因是“棕色”颜色修饰符仍然在中间,并应用于它之前的任何东西。所以我们有

  • U:手指向上
  • M:棕色改性剂
  • L:手指指向左边

original: LMU                    👈🏾👆reversed: UML (above solutions)  ☝🏾👈reversed: ULM (correct reversal) 👆👈🏾

Unicode字位簇比修改代码点要复杂一些。幸运的是,有一个库可以处理字素

>>> 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μsreverse_func        : min:   68.9μs, mean:   70.3μs, max:  111.4μsreverse_reduce      : min:  742.7μs, mean:  810.1μs, max: 1821.9μsreverse_loop        : min:  513.7μs, mean:  552.6μs, max: 1125.8μsreverse_graphemes   : min: 3882.4μs, mean: 4130.9μs, max: 6416.2μs

该守则

#!/usr/bin/env python3
import numpy as npimport randomimport timeitfrom functools import reducerandom.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_strreturn reversed_str

def create_boxplot(title, duration_list, showfliers=False):import seaborn as snsimport matplotlib.pyplot as pltimport operatorplt.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 reverse_a_string(string: str) -> str:"""This method is used to reverse a string.Args:string: a string to reverse
Returns: a reversed string"""if type(string) != str:raise TypeError("{0} This not a string, Please provide a string!".format(type(string)))string_place_holder = ""start = 0end = len(string) - 1if end >= 1:while start <= end:string_place_holder = string_place_holder + string[end]end -= 1return string_place_holderelse:return string

a = "hello world"rev = reverse_a_string(a)print(rev)

输出:

dlrow olleh
 a=input()print(a[::-1])

上面的代码接收来自用户的输入,并通过添加[::-1]打印一个与输入相反的输出。

输出:

>>> Happy>>> yppaH

但是当涉及到句子的情况时,请查看下面的代码输出:

>>> Have a happy day>>> yad yppah a evaH

但是,如果您只想反转字符串的字符而不是字符串的序列,请尝试以下操作:

a=input().split() #Splits the input on the basis of space (" ")for b in a: #declares that var (b) is any value in the list (a)print(b[::-1], end=" ") #End declares to print the character in its quotes (" ") without a new line.

在上面第2行的代码中,我说**变量b是列表(a)中的任何值**我说var a是列表,因为当你在输入中使用拆分时,输入的变量变成了列表。还要记住,在int(输入())的情况下不能使用拆分

输出:

>>> Have a happy day>>> evaH a yppah yad

如果我们不在上面的代码中添加end(""),那么它将打印如下:

>>> Have a happy day>>> evaH>>> a>>> yppah>>> yad

下面是一个理解end()的例子:

代码:

for i in range(1,6):print(i) #Without end()

输出:

>>> 1>>> 2>>> 3>>> 4>>> 5

现在使用end()代码:

for i in range(1,6):print(i, end=" || ")

输出:

>>> 1 || 2 || 3 || 4 || 5 ||

有多种方法可以在Python中反转字符串

切片方法

string = "python"rev_string = string[::-1]print(rev_string)

使用反向函数

string = "python"rev= reversed(string)rev_string = "".join(rev)print(rev_string)

使用递归

string = "python"def reverse(string):if len(string)==0:return stringelse:return reverse(string[1:])+string[0]print(reverse(string))

使用for循环

string = "python"rev_string =""for s in string:rev_string = s+ rev_stringprint(rev_string)

使用if循环

string = "python"rev_str =""length = len(string)-1while length >=0:rev_str += string[length]length -= 1print(rev_str)