代码之家  ›  专栏  ›  技术社区  ›  oneself

在Python中反转字符串

  •  1193
  • oneself  · 技术社区  · 15 年前

    reverse 函数用于Python的 str 对象实现此方法的最佳方式是什么?

    如果提供一个非常简洁的答案,请详细说明其效率。例如,是否 str

    23 回复  |  直到 7 年前
        1
  •  33
  •   Martin Thoma    3 年前

    怎么样:

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

    这是 extended slice 语法。它通过做而起作用 [begin:end:step] -通过禁用“开始”和“结束”并指定步骤-1,它将反转字符串。

        2
  •  1
  •   Soumya Ranjan Rout    4 年前

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

        3
  •  1
  •   Crystalline Core    4 年前

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

    我自己在这个问题上的经验是学术性的。然而,如果你是一个寻找快速答案的专业人士,那么就使用一个循序渐进的片段 -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的字符串,您应该了解以下几点:

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

    2. 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反向_字符串(a_字符串):
    返回一个_字符串[:-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

    最佳做法

    从理论上讲,更好的方法是在列表中收集子字符串,稍后再加入它们:

    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)
    

    以下是时间安排:

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

    may not :

        4
  •  0
  •   Georgy rassa45    5 年前

    实例

    ### 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()?

    问题

    • 上下文
      • Python2.x
      • Python3.x
      • 开发人员希望转换字符串

    解决方案

    • 开发人员可能会期望 string.reverse()
    • 土语 pythonic
    • string.reverse() 避免使用切片表示法。
        • print 'coup_ate_grouping'[-4:] ## => 'ping'
        • 与…相比
        • print 'coup_ate_grouping'[-4:-1] ## => 'pin'
        • 与…相比
        • print 'coup_ate_grouping'[-1] ## => 'g'
      • 对数据进行索引的不同结果 [-1] 可能会把一些开发者甩了

    Python有一个需要注意的特殊情况:字符串是 iterable

    排除 方法是激励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'
    

    结论

    这个 cognitive load

    尽管如此,一旦基本原理被理解,这种方法相对于固定字符串操作方法的力量可能是相当有利的。

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

    另见

        5
  •  0
  •   U13-Forward    3 年前

    在现有解决方案的基础上, , 我的解决方案 .

    只有在忽略Unicode修饰符/字形集簇的情况下,现有答案才是正确的。稍后我将讨论这个问题,但首先看一下一些反转算法的速度:

    enter image description here

    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
    

    enter image description here

    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] )在所有情况下都是最低的(即使在修正了我的打字错误之后)。

    串反转

    如果你真的想按常识反转字符串,那就要复杂得多。例如,以下面的字符串为例( brown finger pointing left yellow finger pointing up ).这是两个字母,但有3个unicode代码点。另外一个是 skin modifier

    example = "👈🏾👆"
    

    但是如果你用任何一种给定的方法来反转它,你会得到 brown finger pointing up yellow finger pointing left . 这样做的原因是“棕色”颜色修饰符仍然在中间并且被应用到它之前的任何东西。所以我们有

    • U:手指向上
    • L:左手指

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