反转Python中的字符串


1348

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

如果提供非常简洁的答案,请详细说明其效率。例如,是否将str对象转换为其他对象等。


1
使用字符串“ tacocat”
JonPizza

Answers:


2649

怎么样:

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

这是扩展片语法。它的工作方式是[begin:end:step]-离开begin和end并指定步骤-1,它反转字符串。


28
但是,这对于utf8不起作用..我也需要这样做,b = a.decode('utf8')[::-1].encode('utf8')但感谢您的正确指导!
Ricky Levi

13
@RickyLevi如果.decode('utf8')需要,则表示a不包含任何字符串对象,而是字节。
Shiplu Mokaddim '17

255

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


13
这大约慢了3倍。
自己

2
简短的评论说出它的作用比使用此较慢的版本会更好地解释它!
tburrows17年

4
速度较慢,因为无论如何join 必须建立列表才能获取大小。''.join(list(reversed(s)))可能会更快。
让·弗朗索瓦·法布尔

您是否了解[[::-1]最快的原因?我想更深入。
Tanner

@Tanner [::-1]最快,因为它不调用任何外部函数,而是使用切片,该切片在python中已高度优化。''.join(list(reversed(s)))进行3个函数调用。
hd1

217

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

我对这个问题的经验是学术上的。但是,如果您是专业人士在寻找快速答案,请使用按-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'

更长的解释

如果您对学术博览会感兴趣,请继续阅读。

Python的str对象中没有内置的反向函数。

您应该了解以下有关Python字符串的几件事:

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

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

    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]

实现为功能

为了实际实现此功能,我认为在语义上足够清晰,只需使用一个描述性名称即可:

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 = a + b形式的语句的就地字符串连接的有效实现。即使在CPython中,这种优化也是脆弱的(仅适用于某些类型),并且在不使用引用计数的实现中根本没有这种优化。在库的性能敏感部分中,应使用''.join()形式。这将确保在各种实现方式中串联发生在线性时间内。


4
我喜欢这个答案,关于优化的解释,可读性与优化,关于老师想要的提示。我不确定最佳实践部分使用while和减少索引,尽管这可能不太容易理解: for i in range(len(a_string)-1, -1, -1): 。我最喜欢的是,您选择的示例字符串是一种您永远不需要将其反转,并且无法告诉您是否具有:)的情况:
达沃斯(Davos)

37

快速解答(TL; DR)

### example01 -------------------
mystring  =   'coup_ate_grouping'
backwards =   mystring[::-1]
print backwards

### ... or even ...
mystring  =   'coup_ate_grouping'[::-1]
print mystring

### result01 -------------------
'''
gnipuorg_eta_puoc
'''

详细答案

背景

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

哇。起初,我对Paolo提出的解决方案感到震惊,但这使我在读了第一条评论时感到的恐惧退缩了:“那太好了。做得好!” 我感到非常不安,以至于这样一个聪明的社区认为将如此神秘的方法用于如此基本的东西是一个好主意。为什么不只是s.reverse()?

问题

  • 语境
    • Python 2.x
    • Python 3.x
  • 场景:
    • 开发人员想要转换字符串
    • 转换是颠倒所有字符的顺序

陷阱

  • 开发人员可能期望像 string.reverse()
  • 较新的开发人员可能无法阅读本机惯用的(又称“ pythonic ”)解决方案
  • 开发人员可能会尝试实施自己的版本,string.reverse()以避免切片符号。
  • 在某些情况下,切片符号的输出可能是违反直觉的:
    • 参见例如example02
      • print 'coup_ate_grouping'[-4:] ## => 'ping'
      • 相比
      • print 'coup_ate_grouping'[-4:-1] ## => 'pin'
      • 相比
      • print 'coup_ate_grouping'[-1] ## => 'g'
    • 建立索引的不同结果[-1]可能会使一些开发人员失望

基本原理

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

排除string.reverse()方法的一个基本原理是给予python开发人员动力以利用这种特殊情况的力量。

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

要了解其工作原理,请查看example02可以提供很好的概述。

示例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: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中的工作方式相关的认知负担确实可能过大。

但是,一旦理解了基本原理,此方法相对于固定字符串操作方法的功能可能会非常有利。

对于那些有其他想法的人,还有其他方法,例如lambda函数,迭代器或简单的一次性函数声明。

如果需要,开发人员可以实现自己的string.reverse()方法,但是最好理解python这方面的原理。

也可以看看


17

仅当忽略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])的时间在所有情况下都是最低的(即使在修正我的错字之后)。

字符串反转

如果您真的想按常识反转字符串,则方法会更加复杂。例如,采用以下字符串(棕色手指指向左黄色手指指向上)。那是两个字素,但有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μ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()

10

1.使用切片符号

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

2.使用reversed()函数

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

1
一定要注意递归解决方案,如果字符串长度合适,则会遇到RecursionError: maximum recursion depth exceeded while calling a Python object。例如:rev_string("abcdef"*1000)
亚当·帕金

9

观察它的一种比较简单的方法是:

string = 'happy'
print(string)

'快乐'

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

'yppah'

用英语[-1 ::-1]读为:

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


2
-1仍然不需要的,虽然。
Eric Duminil

7

不使用reversed()或[::-1]反转python中的字符串

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

1
您是否不应该使用xrange,因为您不需要python 2中的列表?
UnitasBrooks

5

这也是一种有趣的方式:

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

使用支持.reverse()的BYTERArray的另一种“异国情调”方式

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

将产生:

'!siht esreveR'

3
def reverse(input):
    return reduce(lambda x,y : y+x, input)

3
我单击了upvote,因为我喜欢这个lambda表达式。不幸的是,它是上述所有方法中效率最低的解决方案(测试:Gist palindrome.py
oski86

2
original = "string"

rev_index = original[::-1]
rev_func = list(reversed(list(original))) #nsfw

print(original)
print(rev_index)
print(''.join(rev_func))

1
尽管此代码可以回答问题,但最好说明如何解决问题并提供示例或参考代码。仅代码的答案可能会造成混乱,并且缺乏上下文。
罗伯特·哥伦比亚

1
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"

这是通过遍历一个字符串并将其值反向分配给另一个字符串来实现的。


0

这是一个没有幻想的:

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!")

0

这是一个没有[::-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()速度更快。


0

递归方法:

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

0

以上所有解决方案都是完美的,但是如果我们尝试在python中使用for循环来反转字符串会变得有些棘手,所以这是我们如何使用for循环来反转字符串

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

我希望这对某人有帮助。


0

这是我的风格:

def reverse_string(string):
    character_list = []
    for char in string:
        character_list.append(char)
    reversed_string = ""
    for char in reversed(character_list):
        reversed_string += char
    return reversed_string

0

反向字符串有很多方法,但我也创建了另一种方法只是为了好玩。我认为这种方法还不错。

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

0

此类使用python魔术函数反转字符串:

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

使用python 3,您可以就地反转字符串,这意味着它不会被分配给另一个变量。首先,您必须将字符串转换为列表,然后利用该reverse()函数。

https://docs.python.org/3/tutorial/datastructures.html

   def main():
        my_string = ["h","e","l","l","o"]
        print(reverseString(my_string))

    def reverseString(s):
      print(s)
      s.reverse()
      return s

    if __name__ == "__main__":
        main()

-2

这是简单而有意义的反向功能,易于理解和编码

def reverse_sentence(text):
    words = text.split(" ")
    reverse =""
    for word in reversed(words):
        reverse += word+ " "
    return reverse

尽管这可以回答作者的问题,但缺少一些解释性的单词和/或指向文档的链接。没有一些原始短语的话,原始代码片段不是很有用。您可能还会发现如何写一个好的答案很有帮助。请修改您的答案。
hellow

-3

这很简单:

打印“ loremipsum” [-1 ::-1]

从逻辑上讲:

def str_reverse_fun():
    empty_list = []
    new_str = 'loremipsum'
    index = len(new_str)
    while index:
        index = index - 1
        empty_list.append(new_str[index])
    return ''.join(empty_list)
print str_reverse_fun()

输出:

muspimerol


-4

反转没有python魔术的字符串。

>>> def reversest(st):
    a=len(st)-1
    for i in st:
        print(st[a],end="")
        a=a-1

反向打印字符串与反向打印字符串有所不同
Martin Thoma,

-5

当然,在Python中,您可以做非常漂亮的1行内容。:)
这是一个简单,全面的解决方案,可以在任何编程语言中使用。

def reverse_string(phrase):
    reversed = ""
    length = len(phrase)
    for i in range(length):
        reversed += phrase[length-1-i]
    return reversed

phrase = raw_input("Provide a string: ")
print reverse_string(phrase)

1
对于如此琐碎的任务,拥有如此长的代码并不是一个很好的解决方案。
Hunter_71年

-5
s = 'hello'
ln = len(s)
i = 1
while True:
    rev = s[ln-i]
    print rev,
    i = i + 1
    if i == ln + 1 :
        break

输出:

o l l e h

-7

您可以将反向功能与列表综合一起使用。但是我不明白为什么在python 3中取消了这种方法是不必要的。

string = [ char for char in reversed(string)]

这个问题要求输入一个字符串的反向字符,而您却给出一个列表?
user21820 '17

您需要一个.join或一个使其正确答案的内容
AsheKetchum

1
顺便说一句,[c for c in string]等于list(string)
右腿
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.