Wie kehre ich eine Zeichenfolge in Python um?

Lesezeit: 15 Minuten

den eigenen Benutzeravatar
man selbst

Es ist keine eingebaut reverse Funktion für Pythons str Objekt. Wie setzt man diese Methode am besten um?

Wenn Sie eine sehr knappe Antwort geben, erläutern Sie bitte deren Effizienz. Ob zum Beispiel die str Objekt wird in ein anderes Objekt umgewandelt usw.

Benutzeravatar von Paolo Bergantino
Paolo Bergantino

Slicen verwenden:

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

Die Slice-Notation nimmt die Form an [start:stop:step]. In diesem Fall verzichten wir auf die start und stop Positionen, da wir die ganze Zeichenfolge wollen. Wir benützen auch step = -1was bedeutet, “wiederholt um 1 Zeichen von rechts nach links gehen”.

  • Diese Lösung (und die meisten anderen Antworten) funktioniert nicht für den gesamten Unicode, selbst wenn sie auf Python-Unicode-Strings angewendet wird. Zum Beispiel, "🇬🇧"[::-1] Erträge "🇧🇬". Die richtige Lösung ist reversed_string = "".join(list(grapheme.graphemes(input_string))[::-1]). Siehe Martins Antwort unten.

    – Amaurea

    5. April 2021 um 2:00 Uhr

Benutzeravatar von Alex Martelli
Alex Martell

@Paolo s[::-1] ist am schnellsten; ein langsamerer Ansatz (vielleicht lesbarer, aber darüber lässt sich streiten) ist ''.join(reversed(s)).

  • Dies ist etwa 3 mal langsamer.

    – man selbst

    6. Oktober 2017 um 15:09 Uhr


  • Und ein kurzer Kommentar, um zu sagen, was es tut, wird es besser erklären, als diese langsamere Version zu verwenden!

    – Tom Burrows

    2. November 2017 um 22:04 Uhr

  • es ist langsamer, weil join hat um die Liste trotzdem aufzubauen, um die Größe zu bekommen. ''.join(list(reversed(s))) kann etwas schneller sein.

    – Jean-Francois Fabre

    11. Dezember 2017 um 21:34 Uhr

  • Hast du irgendwelche Infos warum [::-1] ist am schnellsten? Ich möchte tiefer eintauchen.

    – Gerber

    30. Oktober 2019 um 17:03 Uhr

  • @Gerber [::-1] ist am schnellsten, weil es keine externen Funktionen aufruft, sondern Slicing verwendet, das in Python hochoptimiert ist. ”.join(list(reversed(s))) macht 3 Funktionsaufrufe.

    – hd1

    27. April 2020 um 13:51 Uhr

Russland muss Putins Benutzer-Avatar entfernen
Russland muss Putin entfernen

Was ist der beste Weg, um eine Umkehrfunktion für Zeichenfolgen zu implementieren?

Meine eigene Erfahrung mit dieser Frage ist akademisch. Wenn Sie jedoch ein Profi sind, der nach der schnellen Antwort sucht, verwenden Sie ein Stück, das schrittweise vorbeikommt -1:

>>> 'a string'[::-1]
'gnirts a'

oder lesbarer (aber langsamer aufgrund der Suche nach Methodennamen und der Tatsache, dass Join eine Liste bildet, wenn ein Iterator angegeben wird), str.join:

>>> ''.join(reversed('a string'))
'gnirts a'

oder fügen Sie den Slice aus Gründen der Lesbarkeit und Wiederverwendbarkeit in eine Funktion ein

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

und dann:

>>> reversed_string('a_string')
'gnirts_a'

Längere Erklärung

Wenn Sie an der akademischen Ausstellung interessiert sind, lesen Sie bitte weiter.

Es gibt keine eingebaute Reverse-Funktion im str-Objekt von Python.

Hier sind ein paar Dinge über Pythons Strings, die Sie wissen sollten:

  1. In Python, Zeichenfolgen sind unveränderlich. Durch das Ändern einer Zeichenfolge wird die Zeichenfolge nicht geändert. Es schafft ein neues.

  2. Saiten sind schneidbar. Durch das Schneiden einer Zeichenfolge erhalten Sie eine neue Zeichenfolge von einem Punkt in der Zeichenfolge rückwärts oder vorwärts zu einem anderen Punkt in bestimmten Schritten. Sie nehmen Slice-Notation oder ein Slice-Objekt in einem Index:

    string[subscript]
    

Der Index erstellt einen Slice, indem er einen Doppelpunkt in die geschweiften Klammern einfügt:

    string[start:stop:step]

Um ein Slice außerhalb der geschweiften Klammern zu erstellen, müssen Sie ein Slice-Objekt erstellen:

    slice_obj = slice(start, stop, step)
    string[slice_obj]

Ein lesbarer Ansatz:

Während ''.join(reversed('foo')) lesbar ist, erfordert es den Aufruf einer String-Methode, str.join, auf einer anderen aufgerufenen Funktion, die relativ langsam sein kann. Setzen wir das in eine Funktion – wir kommen darauf zurück:

def reverse_string_readable_answer(string):
    return ''.join(reversed(string))

Performantster Ansatz:

Viel schneller ist die Verwendung eines umgekehrten Slice:

'foo'[::-1]

Aber wie können wir dies für jemanden, der mit Slices oder der Absicht des ursprünglichen Autors weniger vertraut ist, lesbarer und verständlicher machen? Lassen Sie uns ein Slice-Objekt außerhalb der tiefgestellten Notation erstellen, ihm einen beschreibenden Namen geben und es an die tiefgestellte Notation übergeben.

start = stop = None
step = -1
reverse_slice = slice(start, stop, step)
'foo'[reverse_slice]

Als Funktion implementieren

Um dies tatsächlich als Funktion zu implementieren, ist es meiner Meinung nach semantisch klar genug, einfach einen beschreibenden Namen zu verwenden:

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

Und die Verwendung ist einfach:

reversed_string('foo')

Was dein Lehrer wahrscheinlich will:

Wenn Sie einen Lehrer haben, möchte er wahrscheinlich, dass Sie mit einer leeren Saite beginnen und eine neue Saite aus der alten aufbauen. Sie können dies mit reiner Syntax und Literalen mithilfe einer While-Schleife tun:

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

Das ist theoretisch schlecht, denn denken Sie daran, Zeichenfolgen sind unveränderlich – also jedes Mal, wenn es so aussieht, als würden Sie ein Zeichen an Ihr anhängen new_string, es erstellt theoretisch jedes Mal eine neue Zeichenfolge! CPython weiß jedoch, wie dies in bestimmten Fällen optimiert werden kann, von denen dieser triviale Fall einer ist.

Beste Übung

Theoretisch besser ist es, Ihre Teilstrings in einer Liste zu sammeln und später zusammenzufügen:

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)

Wie wir jedoch in den folgenden Timings für CPython sehen werden, dauert dies tatsächlich länger, da CPython die Zeichenfolgenverkettung optimieren kann.

Zeiten

Hier sind die Zeiten:

>>> 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 optimiert die Zeichenfolgenverkettung, während andere Implementierungen nicht dürfen:

… verlassen Sie sich nicht auf CPythons effiziente Implementierung der direkten Zeichenfolgenverkettung für Anweisungen in der Form a += b oder a = a + b . Diese Optimierung ist selbst in CPython anfällig (sie funktioniert nur für einige Typen) und ist in Implementierungen, die kein Refcounting verwenden, überhaupt nicht vorhanden. In leistungsempfindlichen Teilen der Bibliothek sollte stattdessen die Form „.join()“ verwendet werden. Dadurch wird sichergestellt, dass die Verkettung über verschiedene Implementierungen hinweg in linearer Zeit erfolgt.

  • Ich liebe diese Antwort, Erklärungen zu Optimierungen, Lesbarkeit vs. Optimierung, Tipps zu den Wünschen des Lehrers. Ich bin mir nicht sicher über den Best-Practice-Abschnitt mit dem while und den Index dekrementieren, obwohl dies vielleicht weniger lesbar ist: for i in range(len(a_string)-1, -1, -1): . Am meisten finde ich es toll, dass die von Ihnen gewählte Beispielzeichenfolge der einzige Fall ist, in dem Sie sie niemals umkehren müssten und nicht sagen könnten, ob Sie es getan hätten 🙂

    – Davos

    14. August 2019 um 15:38 Uhr


Benutzeravatar von dreftymac
dreftymac

Schnelle Antwort (TL;DR)

Beispiel

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

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

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

Ausführliche Antwort

Hintergrund

Diese Antwort wird bereitgestellt, um das folgende Anliegen von @odigity anzusprechen:

Wow. Zuerst war ich entsetzt über die von Paolo vorgeschlagene Lösung, aber das trat in den Hintergrund des Entsetzens, das ich empfand, als ich den ersten Kommentar las: „Das ist sehr pythonisch. Gute Arbeit!“ Ich bin so beunruhigt, dass eine so kluge Community denkt, dass es eine gute Idee ist, solch kryptische Methoden für etwas so Grundlegendes zu verwenden. Warum liegt es nicht einfach an reverse()?

Problem

  • Kontext
    • Python 2.x
    • Python 3.x
  • Szenario:
    • Der Entwickler möchte eine Zeichenfolge umwandeln
    • Die Transformation erfolgt in umgekehrter Reihenfolge aller Zeichen

Lösung

Tücken

  • Der Entwickler erwartet möglicherweise etwas wie string.reverse()
  • Die native idiomatische (alias „pythonische“) Lösung ist möglicherweise für neuere Entwickler nicht lesbar
  • Entwickler könnten versucht sein, ihre eigene Version von zu implementieren string.reverse() Slice-Notation zu vermeiden.
  • Die Ausgabe der Slice-Notation kann in einigen Fällen kontraintuitiv sein:
    • siehe zB Beispiel02
      • print 'coup_ate_grouping'[-4:] ## => 'ping'
      • verglichen mit
      • print 'coup_ate_grouping'[-4:-1] ## => 'pin'
      • verglichen mit
      • print 'coup_ate_grouping'[-1] ## => 'g'
    • die unterschiedlichen Ergebnisse der Indizierung auf [-1] kann einige Entwickler abschrecken

Begründung

Python hat einen besonderen Umstand zu beachten: ein String ist ein wiederholbar Art.

Ein Grund für den Ausschluss von a string.reverse() Die Methode besteht darin, Python-Entwicklern einen Anreiz zu geben, die Macht dieser besonderen Umstände zu nutzen.

Vereinfacht ausgedrückt bedeutet dies einfach, dass jedes einzelne Zeichen in einer Zeichenfolge einfach als Teil einer sequentiellen Anordnung von Elementen bearbeitet werden kann, genau wie Arrays in anderen Programmiersprachen.

Um zu verstehen, wie dies funktioniert, kann die Überprüfung von example02 einen guten Überblick geben.

Beispiel02

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

Fazit

Die kognitive Belastung in Verbindung mit dem Verständnis, wie die Slice-Notation in Python funktioniert, kann für einige Anwender und Entwickler, die nicht viel Zeit in das Erlernen der Sprache investieren möchten, tatsächlich zu viel sein.

Sobald die Grundprinzipien verstanden sind, kann die Leistungsfähigkeit dieses Ansatzes gegenüber festen String-Manipulationsmethoden jedoch recht günstig sein.

Für diejenigen, die anderer Meinung sind, gibt es alternative Ansätze wie Lambda-Funktionen, Iteratoren oder einfache einmalige Funktionsdeklarationen.

Auf Wunsch kann ein Entwickler seine eigene string.reverse()-Methode implementieren, es ist jedoch gut, die Gründe für diesen Aspekt von Python zu verstehen.

Siehe auch

  • alternativer einfacher Ansatz
  • alternativer einfacher Ansatz
  • alternative Erklärung der Slice-Notation

Benutzeravatar von Martin Thoma
Martin Thomas

Diese Antwort ist etwas länger und enthält 3 Abschnitte: Benchmarks bestehender Lösungen, warum die meisten Lösungen hier falsch sind, meine Lösung.

Die vorhandenen Antworten sind nur richtig, wenn Unicode-Modifikatoren / Graphem-Cluster ignoriert werden. Darauf gehe ich später ein, aber werfen Sie zunächst einen Blick auf die Geschwindigkeit einiger Umkehralgorithmen:

Geben Sie hier die Bildbeschreibung ein

HINWEIS: Ich habe, was ich angerufen habe list_comprehension aufgerufen werden soll slicing

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

Geben Sie hier die Bildbeschreibung ein

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

Sie können sehen, dass die Zeit für das Listenverständnis (reversed = string[::-1]) ist in allen Fällen bei weitem am niedrigsten (auch nachdem ich meinen Tippfehler behoben habe).

Saitenumkehrung

Wenn Sie wirklich eine Zeichenfolge im gesunden Menschenverstand umkehren möchten, ist es viel komplizierter. Nehmen Sie zum Beispiel die folgende Zeichenfolge (brauner Finger, der nach links zeigt, gelber Finger nach oben). Das sind zwei Grapheme, aber 3 Unicode-Codepunkte. Der zusätzliche ist ein Hautmodifikator.

example = "👈🏾👆"

Aber wenn Sie es mit einer der angegebenen Methoden umkehren, erhalten Sie brauner Finger nach oben, gelber Finger nach links. Der Grund dafür ist, dass der Farbmodifikator “Braun” immer noch in der Mitte ist und auf alles angewendet wird, was davor ist. Also haben wir

  • U: Finger zeigt nach oben
  • M: brauner Modifikator
  • L: Finger zeigt nach links

und

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

Unicode-Graphem-Cluster sind etwas komplizierter als nur Modifikator-Codepunkte. Zum Glück gibt es eine Bibliothek für die Handhabung Grapheme:

>>> import grapheme
>>> g = grapheme.graphemes("👈🏾👆")
>>> list(g)
['👈🏾', '👆']

und daher wäre die richtige Antwort

def reverse_graphemes(string):
    g = list(grapheme.graphemes(string))
    return ''.join(g[::-1])

das ist auch bei weitem das langsamste:

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

Der Code

#!/usr/bin/env python3

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

  • Vielen Dank für das Zeigen der richtigen Graphem-bewussten String-Umkehrung. Für fast jeden realistischen Zweck sind alle anderen Antworten hier falsch. Schade, dass Sie weniger als 1 % der Stimmen für die beliebteste Antwort haben.

    – Amaurea

    5. April 2021 um 1:50 Uhr

  • Es ist jedoch ein bisschen unglücklich, dass Ihre Lösung inmitten vieler Benchmarking-Sachen halb versteckt ist. Ich würde es viel früher und prominenter setzen und vielleicht explizit zeigen, wie die einfachen Lösungen der anderen schief gehen (Sie beschreiben es, aber zeigen es nicht). Flaggen-Emojis sind ebenfalls gute Beispiele dafür.

    – Amaurea

    5. April 2021 um 1:52 Uhr

  • Gut zu sehen, dass es sich lohnt, nicht nur die Antworten am Anfang zu lesen. Übrigens: Wäre es nicht besser, die Statistiken (einschließlich Ihrer graphemfähigen Version) am Ende zu bringen?

    – Wolf

    11. September 2022 um 17:38 Uhr


  • Warum tut Ihr list_comprehension Funktion enthält keine Liste Comprehensions?

    – Kelwood

    11. Dezember 2022 um 7:47 Uhr

  • @khelwood Du hast Recht, “Slicing” wäre ein besserer Name gewesen. Ich bin jetzt allerdings zu faul, die ganze Grafik anzupassen … vielleicht wäre ein “3.11”-Update aber interessant

    – Martin Thoma

    12. Dezember 2022 um 13:23 Uhr

1. Verwendung der Slice-Notation

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

2. Verwendung der Funktion reversed()

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

3. Verwendung von Rekursion

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

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

  • Vielen Dank für das Zeigen der richtigen Graphem-bewussten String-Umkehrung. Für fast jeden realistischen Zweck sind alle anderen Antworten hier falsch. Schade, dass Sie weniger als 1 % der Stimmen für die beliebteste Antwort haben.

    – Amaurea

    5. April 2021 um 1:50 Uhr

  • Es ist jedoch ein bisschen unglücklich, dass Ihre Lösung inmitten vieler Benchmarking-Sachen halb versteckt ist. Ich würde es viel früher und prominenter setzen und vielleicht explizit zeigen, wie die einfachen Lösungen der anderen schief gehen (Sie beschreiben es, aber zeigen es nicht). Flaggen-Emojis sind ebenfalls gute Beispiele dafür.

    – Amaurea

    5. April 2021 um 1:52 Uhr

  • Gut zu sehen, dass es sich lohnt, nicht nur die Antworten am Anfang zu lesen. Übrigens: Wäre es nicht besser, die Statistiken (einschließlich Ihrer graphemfähigen Version) am Ende zu bringen?

    – Wolf

    11. September 2022 um 17:38 Uhr


  • Warum tut Ihr list_comprehension Funktion enthält keine Liste Comprehensions?

    – Kelwood

    11. Dezember 2022 um 7:47 Uhr

  • @khelwood Du hast Recht, “Slicing” wäre ein besserer Name gewesen. Ich bin jetzt allerdings zu faul, die ganze Grafik anzupassen … vielleicht wäre ein “3.11”-Update aber interessant

    – Martin Thoma

    12. Dezember 2022 um 13:23 Uhr

Benutzeravatar von pX0r
pX0r

Eine weniger verwirrende Betrachtungsweise wäre:

string = 'happy'
print(string)

‘glücklich’

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

‘yppah’

Auf Englisch [-1::-1] liest sich wie folgt:

“Beginnen Sie bei -1, gehen Sie den ganzen Weg und machen Sie Schritte von -1”

  • Die -1 ist aber immer noch unnötig.

    – Eric Duminil

    13. März 2019 um 16:30 Uhr

  • @EricDuminil, wenn Sie es verstehen möchten, ist es meiner Meinung nach erforderlich

    – Kyoko Sasagava

    2. August 2021 um 8:20 Uhr

1440730cookie-checkWie kehre ich eine Zeichenfolge in Python um?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy