web-dev-qa-db-fra.com

Inverser une chaîne en Python

Il n'y a pas de fonction reverse intégrée pour l'objet str de Python. Quel est le meilleur moyen de mettre en œuvre cette méthode?

Si vous fournissez une réponse très concise, veuillez préciser son efficacité. Par exemple, si l'objet str est converti en un objet différent, etc. 

1190
oneself

Que diriez-vous:

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

C'est extended slice syntax. Cela fonctionne en faisant [begin:end:step] - en laissant début et fin et en spécifiant un pas de -1, il inverse une chaîne.

2430
Paolo Bergantino

Le s[::-1] de @ Paolo est le plus rapide; une approche plus lente (peut-être plus lisible, mais c'est discutable) est ''.join(reversed(s)).

236
Alex Martelli

Quel est le meilleur moyen d'implémenter une fonction inverse pour les chaînes?

Ma propre expérience avec cette question est académique. Toutefois, si vous êtes un professionnel à la recherche de la réponse rapide, utilisez une tranche qui passe par -1:

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

ou plus lisiblement (mais plus lentement à cause des recherches de nom de méthode et du fait que join forme une liste quand un itérateur est donné), str.join:

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

ou pour des raisons de lisibilité et de réutilisabilité, placez la tranche dans une fonction

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

et alors:

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

Explication plus longue

Si vous êtes intéressé par l'exposition académique, continuez à lire.

Il n'y a pas de fonction inverse intégrée dans l'objet str de Python. 

Voici quelques informations sur les chaînes de Python que vous devriez connaître:

  1. En Python, les chaînes sont immuables. Changer une chaîne ne modifie pas la chaîne. Cela en crée un nouveau.

  2. Les cordes sont sliceable. Trancher une chaîne vous donne une nouvelle chaîne d'un point dans la chaîne, en avant ou en arrière, à un autre point, par incréments donnés. Ils prennent la notation slice ou un objet slice en indice:

    string[subscript]
    

L'indice crée une tranche en incluant deux points entre les accolades:

    string[start:stop:step]

Pour créer une tranche en dehors des accolades, vous devez créer un objet tranche:

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

Une approche lisible:

Alors que ''.join(reversed('foo')) est lisible, il faut appeler une méthode de chaîne, str.join, sur une autre fonction appelée, qui peut être relativement lente. Mettons cela dans une fonction - nous y reviendrons:

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

Approche la plus performante:

Beaucoup plus rapidement utilise une tranche inversée:

'foo'[::-1]

Mais comment pouvons-nous rendre cela plus lisible et compréhensible pour quelqu'un de moins au courant des tranches ou de l'intention de l'auteur original? Créons un objet slice en dehors de la notation en indice, attribuons-lui un nom descriptif et passons-le à la notation en indice.

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

Mettre en œuvre en tant que fonction

Pour réellement implémenter cela en tant que fonction, je pense qu'il est suffisamment clair sur le plan sémantique pour utiliser simplement un nom descriptif:

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

Et l'utilisation est simplement:

reversed_string('foo')

Ce que votre professeur veut probablement:

Si vous avez un instructeur, il voudra probablement que vous commenciez avec une chaîne vide et que vous construisiez une nouvelle chaîne à partir de l’ancienne. Vous pouvez le faire avec de la syntaxe pure et des littéraux en utilisant une boucle 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

Ceci est théoriquement mauvais parce que, rappelez-vous, les chaînes sont immuables - donc chaque fois que vous ajoutez un caractère à votre new_string, vous créez théoriquement une nouvelle chaîne à chaque fois! Cependant, CPython sait comment optimiser cela dans certains cas, dont ce cas trivial.

Meilleur entrainement

Théoriquement, il est préférable de rassembler vos sous-chaînes dans une liste et de les rejoindre plus tard:

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)

Toutefois, comme nous le verrons dans les timings ci-dessous pour CPython, cela prend plus de temps, car CPython peut optimiser la concaténation de chaînes.

Les horaires

Voici les timings:

>>> 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 optimise la concaténation de chaînes, alors que d'autres implémentations peuvent ne pas :

... ne vous fiez pas à la mise en oeuvre efficace par CPython de la concaténation sur place de chaînes pour les instructions de la forme a + = b ou a = a + b. Cette optimisation est fragile même dans CPython (cela ne fonctionne que pour certains types) et n'est pas présente du tout dans les implémentations qui n'utilisent pas refcounting. Dans les parties de la bibliothèque sensibles aux performances, le formulaire '' .join () doit être utilisé à la place. Cela garantira que la concaténation se produise dans le temps linéaire à travers différentes implémentations. 

188
Aaron Hall

Réponse rapide (TL; DR)

Exemple

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

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

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

Réponse détaillée

Contexte

Cette réponse est fournie pour répondre à la préoccupation suivante de @odigity:

Sensationnel. J'ai d'abord été horrifié par la solution proposée par Paolo, mais cela pris la banquette arrière de l'horreur que j'ai ressentie en lisant le premier commentaire: "C'est très Pythonique. Bon travail!" Je suis tellement dérangé que tel une communauté brillante pense utiliser de telles méthodes cryptiques pour quelque chose de tellement la base est une bonne idée. Pourquoi n'est-ce pas juste à l'inverse ()?

Problème

  • Le contexte
    • Python 2.x
    • Python 3.x
  • Scénario:
    • Le développeur veut transformer une chaîne
    • La transformation consiste à inverser l'ordre de tous les personnages

Solution

Les pièges

  • Le développeur peut s'attendre à quelque chose comme string.reverse()
  • La solution native idiomatique (" Pythonic ") peut ne pas être lisible par les développeurs plus récents.
  • Le développeur peut être tenté d'implémenter sa propre version de string.reverse() pour éviter la notation par tranches.
  • La sortie de la notation de tranche peut être contre-intuitive dans certains cas:
    • voir par exemple, exemple02
      • print 'coup_ate_grouping'[-4:] ## => 'ping'
      • par rapport à
      • print 'coup_ate_grouping'[-4:-1] ## => 'pin'
      • par rapport à
      • print 'coup_ate_grouping'[-1] ## => 'g'
    • les différents résultats de l'indexation sur [-1] peuvent décourager certains développeurs

Raisonnement

Python a une circonstance spéciale à prendre en compte: une chaîne est un type iterable .

Une des raisons pour exclure une méthode string.reverse() est d'encourager les développeurs python à tirer parti de la puissance de cette circonstance particulière.

En termes simplifiés, cela signifie simplement que chaque caractère d'une chaîne peut être facilement utilisé dans le cadre d'une disposition séquentielle d'éléments, tout comme les tableaux dans d'autres langages de programmation.

Pour comprendre comment cela fonctionne, vous pouvez passer en revue exemple02 pour obtenir un bon aperçu.

Exemple02

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

Conclusion

La charge cognitive associée à la compréhension du fonctionnement de la notation slice en python est peut-être trop pour certains adoptants et développeurs qui ne souhaitent pas investir beaucoup de temps dans l'apprentissage du langage.

Néanmoins, une fois que les principes de base sont compris, le pouvoir de cette approche par rapport aux méthodes de manipulation de chaînes fixes peut être très favorable.

Pour ceux qui pensent le contraire, il existe des approches alternatives, telles que les fonctions lambda, les itérateurs ou les déclarations de fonctions simples et ponctuelles.

Si cela est souhaité, un développeur peut implémenter sa propre méthode string.reverse (), mais il est bon de comprendre la logique qui sous-tend cet aspect de python.

Voir également

34
dreftymac

Une façon moins compliquée de voir les choses serait:

string = 'happy'
print(string)

'heureux'

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

'yppah'

En anglais [-1 :: - 1] se lit comme suit:

"À partir de -1, allez jusqu'au bout, en prenant des mesures de -1"

10
pX0r

en utilisant la notation de tranche

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

en utilisant la fonction reverse ()

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

en utilisant la récursion

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

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

Inverser une chaîne en python sans utiliser reverse () ou [:: - 1]

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

C'est aussi un moyen intéressant:

def reverse_words_1(s):
    rev = ''
    for i in range(len(s)):
        j = ~i  # equivalent to j = -(i + 1)
        rev += s[j]
    return rev

ou similaire:

def reverse_words_2(s):
    rev = ''
    for i in reversed(range(len(s)):
        rev += s[i]
    return rev

Une autre manière plus "exotique" d'utiliser Byterarray qui prend en charge .reverse ()

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

produira:

'!siht esreveR'
3
mdn

Les réponses existantes ne sont correctes que si les modificateurs Unicode/grappes de graphèmes sont ignorés. J'y reviendrai plus tard, mais examinons d'abord la vitesse de certains algorithmes d'inversion:

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

Vous pouvez voir que le temps pour la compréhension de la liste (reversed = string[::-1]) est dans tous les cas de loin le plus bas (même après avoir corrigé ma faute de frappe).

Annulation de chaîne

Si vous voulez vraiment inverser une chaîne au sens commun, c'est beaucoup plus compliqué. Par exemple, prenons la chaîne suivante ( doigt brun pointant à gauche _ _ doigt jaune pointant vers le haut ). Ce sont deux graphèmes, mais 3 points de code Unicode. Le complément est un modificateur de peau .

example = "????????????"

Mais si vous inversez avec l'une des méthodes données, vous obtenez le doigt brun qui pointe vers le haut , le doigt jaune qui pointe vers la gauche . La raison en est que le modificateur de couleur "marron" est toujours au milieu et est appliqué à tout ce qui se trouve devant lui. Donc nous avons

  • U: doigt pointé vers le haut
  • M: modificateur brun
  • L: doigt pointé vers la gauche

et

original: LMU
reversed: UML (above solutions)
reversed: ULM (correct reversal)

_ { Unicode Grapheme Clusters } _ sont un peu plus compliqués que de simples points de code modificateurs. Heureusement, il existe une bibliothèque pour manipuler graphemes :

>>> import grapheme
>>> g = grapheme.graphemes("????????????")
>>> list(g)
['????????', '????']

et donc la bonne réponse serait

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

qui est aussi de loin le plus lent:

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

Le code

#!/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

Voici un pas de fantaisie:

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

C’est ma façon:

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

Toutes les solutions ci-dessus sont parfaites, mais si nous essayons d'inverser une chaîne en utilisant une boucle for en python, cela deviendra un peu délicat.

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

J'espère que celui-ci sera utile à quelqu'un.

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"

Cela fonctionne en boucle sur une chaîne et en affectant ses valeurs dans l'ordre inverse à une autre chaîne.

original = "string"

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

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

Il y a beaucoup de façons d'inverser une chaîne, mais j'ai aussi créé une autre juste pour le fun. Je pense que cette approche n'est pas si mauvaise.

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

Méthode récursive:

def reverse(s): return s[0] if len(s)==1 else s[len(s)-1] + reverse(s[0:len(s)-1])

exemple:

print(reverse("Hello!"))    #!olleH
0
matt

En voici un sans [::-1] ou reversed (à des fins d'apprentissage):

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

vous pouvez utiliser += pour concaténer des chaînes, mais join() est plus rapide. 

0
Claudiu Creanga

Je ne sais pas sur l'efficacité mais une solution courte trouvée sur le MIT lectures est

s = "abcd"

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

Vous pouvez en savoir plus sur Slices pour mieux comprendre ce code.

0
Krishnadas PC

Inverser une chaîne sans magie python.

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