在Python中反转一个字符串

one*_*elf 1288 python string

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

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

Pao*_*ino 2565

怎么样:

>>> 'hello world'[::-1]
'dlrow olleh'
Run Code Online (Sandbox Code Playgroud)

这是扩展切片语法.它的工作原理是[begin:end:step]- 通过离开开始和结束并指定步长为-1,它会反转一个字符串.

  • 这虽然不适用于utf8 ..我也需要这样做`b = a.decode('utf8')[:: - 1] .encode('utf8')`但是感谢正确的方向! (26认同)
  • @RickyLevi如果需要`.decode('utf8')`,则表示`a`不包含任何字符串对象,而是字节. (10认同)
  • 该解决方案(以及大多数其他答案)并不适用于所有 unicode,即使应用于 Python unicode 字符串也是如此。例如,`""[::-1]` 产生 `""`。正确的解决方案是 `reversed_string = "".join(list(grapheme.graphemes(input_string))[::-1])`。请参阅下面马丁的回答。 (10认同)

Ale*_*lli 252

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

  • 这大约慢3倍. (11认同)
  • 它更慢,因为`join` _has_无论如何都要构建列表以便能够获得大小.`''.join(list(reversed(s)))`可能会稍快一些. (4认同)
  • 并且快速评论说它的作用将比使用这个更慢的版本更好地解释它! (2认同)
  • @Tanner [::-1] 是最快的,因为它不调用任何外部函数,而是使用切片,这在 python 中是高度优化的。''.join(list(reversed(s))) 进行 3 次函数调用。 (2认同)

Aar*_*all 208

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

我对这个问题的经验是学术性的.但是,如果您是专业人士寻找快速答案,请使用逐步执行的切片-1:

>>> 'a string'[::-1]
'gnirts a'
Run Code Online (Sandbox Code Playgroud)

或者更可读(但由于方法名称查找速度较慢而且在给定迭代器时连接形成列表的事实)str.join:

>>> ''.join(reversed('a string'))
'gnirts a'
Run Code Online (Sandbox Code Playgroud)

或者为了可读性和可重用性,将切片放在一个函数中

def reversed_string(a_string):
    return a_string[::-1]
Run Code Online (Sandbox Code Playgroud)

然后:

>>> reversed_string('a_string')
'gnirts_a'
Run Code Online (Sandbox Code Playgroud)

更长的解释

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

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

这里有一些你应该知道的Python字符串:

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

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

    string[subscript]
    
    Run Code Online (Sandbox Code Playgroud)

下标通过在大括号中包含冒号来创建切片:

    string[start:stop:step]
Run Code Online (Sandbox Code Playgroud)

要在大括号外创建切片,您需要创建切片对象:

    slice_obj = slice(start, stop, step)
    string[slice_obj]
Run Code Online (Sandbox Code Playgroud)

一种可读的方法:

虽然''.join(reversed('foo'))是可读的,但它需要str.join在另一个被调用的函数上调用字符串方法,这可能相当慢.让我们把它放在一个函数中 - 我们将回到它:

def reverse_string_readable_answer(string):
    return ''.join(reversed(string))
Run Code Online (Sandbox Code Playgroud)

最有效的方法:

使用反向切片要快得多:

'foo'[::-1]
Run Code Online (Sandbox Code Playgroud)

但是,对于不太熟悉切片或原作者意图的人,我们怎样才能使这个更具可读性和易懂性?让我们在下标表示法之外创建一个切片对象,给它一个描述性名称,并将其传递给下标符号.

start = stop = None
step = -1
reverse_slice = slice(start, stop, step)
'foo'[reverse_slice]
Run Code Online (Sandbox Code Playgroud)

实现为功能

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

def reversed_string(a_string):
    return a_string[::-1]
Run Code Online (Sandbox Code Playgroud)

用法很简单:

reversed_string('foo')
Run Code Online (Sandbox Code Playgroud)

老师可能想要的东西:

如果你有一个教师,他们可能希望你从一个空字符串开始,并从旧的字符串建立一个新的字符串.您可以使用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
Run Code Online (Sandbox Code Playgroud)

这在理论上是不好的,因为,记住,字符串是不可变的 - 所以每次看起来你在你的字符上附加一个字符时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)
Run Code Online (Sandbox Code Playgroud)

但是,正如我们将在下面的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
Run Code Online (Sandbox Code Playgroud)

CPython优化字符串连接,而其他实现可能不:

...不要依赖CPython为a + = b或a = a + b形式的语句高效实现就地字符串连接.即使在CPython中,这种优化也很脆弱(它只适用于某些类型),并且在不使用引用计数的实现中根本不存在.在库的性能敏感部分,应该使用'.join()形式.这将确保在各种实现中以线性时间进行连接.

  • 我喜欢这个答案,关于优化的解释,可读性与优化,关于老师想要的提示。我不确定使用`while`和减少索引的最佳实践部分,尽管也许可读性较差:`for i in range(len(a(string)-1,-1,-1):`。最重要的是,我喜欢您选择的示例字符串是一种情况,您永远无需将其反向,并且无法告诉您是否有:) (3认同)

dre*_*mac 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
'''
Run Code Online (Sandbox Code Playgroud)

详细解答

背景

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

哇.起初Paolo提议的解决方案让我感到震惊,但是在阅读第一条评论时我感到恐惧的后座:"这是非常pythonic.干得好!" 我很不安,这样一个聪明的社区认为使用这种神秘的方法来做一些基本的东西是个好主意.为什么不是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可以提供一个很好的概述.

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'
Run Code Online (Sandbox Code Playgroud)

结论

与理解切片符号在python中如何工作相关的认知负荷对于那些不希望花费大量时间学习语言的采用者和开发者来说确实太过分了.

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

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

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

也可以看看


Mar*_*oma 12

仅当忽略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
Run Code Online (Sandbox Code Playgroud)

在此处输入图片说明

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
Run Code Online (Sandbox Code Playgroud)

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

字符串反转

如果您真的想按常识反转字符串,则方法会更加复杂。例如,采用以下字符串(棕色手指指向左黄色手指指向上)。那是两个字素,但有3个unicode码点。另一个是皮肤修饰剂

example = ""
Run Code Online (Sandbox Code Playgroud)

但是,如果使用任何给定的方法将其反转,则会使棕色手指指向上方黄色手指指向左侧。这样做的原因是“棕色”颜色修改器仍在中间,并应用于之前的任何内容。所以我们有

  • U:手指向上
  • M:棕色修饰剂
  • L:手指指向左

original: LMU
reversed: UML (above solutions)
reversed: ULM (correct reversal)
Run Code Online (Sandbox Code Playgroud)

Unicode音素簇比修饰符代码点要复杂一些。幸运的是,用于处理库字形

>>> import grapheme
>>> g = grapheme.graphemes("")
>>> list(g)
['', '']
Run Code Online (Sandbox Code Playgroud)

因此正确的答案是

def reverse_graphemes(string):
    g = list(grapheme.graphemes(string))
    return ''.join(g[::-1])
Run Code Online (Sandbox Code Playgroud)

到目前为止也是最慢的:

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
Run Code Online (Sandbox Code Playgroud)

代码

#!/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()
Run Code Online (Sandbox Code Playgroud)

  • 不过,有点不幸的是,您的解决方案半隐藏在大量基准测试内容中。我会把它放在更早、更显着的位置,也许会明确地展示其他人给出的简单解决方案是如何出错的(你描述它但不展示它)。旗帜表情符号也是很好的例子。 (4认同)
  • 感谢您展示正确的字形感知字符串反转。对于几乎任何现实目的,这里所有其他答案都是错误的。但遗憾的是,您在最受欢迎答案中的得票率还不到 1%。 (2认同)
  • 很高兴看到它不仅值得阅读开头的答案。顺便说一句:把统计数据(包括你的字形感知版本)放在最后不是更好吗? (2认同)

pX0*_*X0r 10

看待它的一个较小的令人困惑的方式是:

string = 'happy'
print(string)
Run Code Online (Sandbox Code Playgroud)

'快乐'

string_reversed = string[-1::-1]
print(string_reversed)
Run Code Online (Sandbox Code Playgroud)

"帕"

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

"从-1开始,一路走,步长为-1"

  • 但是,仍然不需要`-1`。 (2认同)
  • @EricDuminil,如果你想理解,我认为这是需要的 (2认同)

har*_*rry 10

使用切片表示法

def rev_string(s): 
    return s[::-1]
Run Code Online (Sandbox Code Playgroud)

使用reverse()函数

def rev_string(s): 
    return ''.join(reversed(s))
Run Code Online (Sandbox Code Playgroud)

使用递归

def rev_string(s): 
    if len(s) == 1:
        return s

    return s[-1] + rev_string(s[:-1])
Run Code Online (Sandbox Code Playgroud)

  • 必须注意递归解决方案,如果字符串长度合适,您将遇到“RecursionError:调用 Python 对象时超出最大递归深度”。例如:`rev_string("abcdef"*1000)` (2认同)

aks*_*pal 5

在不使用reverse()或[:: - 1]的情况下在python中反转字符串

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


gxp*_*xpr 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
Run Code Online (Sandbox Code Playgroud)

或类似:

def reverse_words_2(s):
    rev = ''
    for i in reversed(range(len(s)):
        rev += s[i]
    return rev
Run Code Online (Sandbox Code Playgroud)

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

b = bytearray('Reverse this!', 'UTF-8')
b.reverse()
b.decode('UTF-8')
Run Code Online (Sandbox Code Playgroud)

将产生:

'!siht esreveR'
Run Code Online (Sandbox Code Playgroud)