web-dev-qa-db-ja.com

Pythonで文字列を反転する

Pythonのreverseオブジェクトには組み込みのstr関数はありません。この方法を実装するための最良の方法は何ですか?

非常に簡潔な答えを提供するならば、その効率について詳しく述べてください。たとえば、strオブジェクトが別のオブジェクトに変換されるかどうかなどです。 

1190
oneself

どうですか?

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

これは 拡張スライス 構文です。これは[begin:end:step]を実行することで動作します - beginとendをオフのままにして-1のステップを指定することで、文字列を反転します。

2430

@ Paoloのs[::-1]が最速です。もっと遅いアプローチ(もっと読みやすいかもしれませんが議論の余地があります)は''.join(reversed(s))です。

236
Alex Martelli

文字列の逆関数を実装する最良の方法は何ですか?

この質問に対する私自身の経験は学術的です。ただし、簡単な答えを探しているプロの場合は、-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_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は文字列の連結を最適化しますが、他の実装では ない場合があります

... a + = bまたはa = a + bの形式のステートメントに対するCPythonのインプレース文字列連結の効率的な実装に依存しないでください。この最適化はCPythonでも脆弱で(一部のタイプでのみ機能します)、refcountingを使用しない実装ではまったく存在しません。ライブラリのパフォーマンスに敏感な部分では、代わりに '' .join()形式を使用する必要があります。これにより、さまざまな実装で線形時間で連結が発生します。

196
Aaron Hall

クイックアンサー(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からの以下の懸念に対処するために提供されています。

ワオ。私は最初、パオロが提案した解決策に恐怖を感じましたが、が最初のコメントを読んだときに感じた恐怖を後退させました。そのような明るいコミュニティは、とても基本的なのような暗号化された方法を使用することをお勧めします。どうして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には特別な事情があります。文字列は iterable 型です。

string.reverse()メソッドを排除するための1つの論理的根拠は、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'

結論

スライス記法がPythonでどのように機能するかを理解することに関連する 認知的負荷 は、実際には言語の学習に多くの時間を費やすことを望まない一部の採用者および開発者にとっては多すぎるかもしれません。

それにもかかわらず、いったん基本原理が理解されれば、固定文字列操作方法に対するこのアプローチの力は非常に有利になり得る。

そうではないと考える人には、ラムダ関数、イテレータ、単純な1回限りの関数宣言など、別の方法があります。

望むなら、開発者は自分自身のstring.reverse()メソッドを実装することができますが、pythonのこの側面の背後にある理論的根拠を理解するのは良いことです。

また見なさい

34
dreftymac

それを見るためのより厄介な方法は次のとおりです。

string = 'happy'
print(string)

'ハッピー'

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

イッパ

英語の場合、[-1 :: - 1]は次のようになります。

"-1から始めて、-1のステップを踏んで、ずっと走りなさい"

10
pX0r

スライス表記を使う

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

reverse()関数を使う

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

Backward()または[:: - 1]を使用せずにpythonで文字列を反転

def reverse(test):
    n = len(test)
    x=""
    for i in range(n-1,-1,-1):
        x += test[i]
    return x
5
akshaynagpal
def reverse(input):
    return reduce(lambda x,y : y+x, input)
3
Javier

これもまた興味深い方法です。

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()をサポートするバイト配列を使用したもう1つのより「エキゾチックな」方法

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

生成されます:

'!siht esreveR'
3
mdn

既存の答えは、Unicode Modifiers /書記素クラスタが無視された場合にのみ正しいものです。これについては後で説明しますが、最初にいくつかの逆転アルゴリズムの速度を見てみましょう。

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])の時間は、(私のタイプミスを修正した後でも)すべてのケースではるかに低いことがわかります。

文字列の反転

もしあなたが本当に常識的に文字列を反転させたいのであれば、それはもっと複雑です。たとえば、次の文字列を取ります( 茶色の指が左を向く黄色の指が上を向く )。これらは2つの書記素ですが、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 __== '__main__':
    main()
2
Martin Thoma

これは空想のないものです:

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

それが私のやり方だ:

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

上記の解決法はすべて完璧ですが、Pythonでforループを使用して文字列を反転させようとすると、少しトリッキーになります。

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

これが誰かに役立つことを願っています。

1
Nitin Khanna
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))
1
JEX

文字列を逆にする方法はたくさんありますが、楽しみのために別の方法も作成しました。このアプローチはそれほど悪くないと思います。

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

再帰的な方法:

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
matt

これは[::-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
Claudiu Creanga

効率についてはわからないが、 _ mit _ 講義で見つけられた短い解決策は

s = "abcd"

s[-1:-(len(s)+1):-1]
Out[5]: 'dcba'

このコードの理解を深めるために、 スライス についてもっと読むことができます。

0
Krishnadas PC

Pythonの魔法なしで文字列を反転します。

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