¿Cómo invierto una cadena en Python?

16 minutos de lectura

avatar de usuario de uno mismo
uno mismo

No hay incorporado reverse función para Python str objeto. ¿Cuál es la mejor manera de implementar este método?

Si proporciona una respuesta muy concisa, detalle su eficiencia. Por ejemplo, si el str el objeto se convierte en un objeto diferente, etc.

Avatar de usuario de Paolo Bergantino
paolo bergantino

Usando rebanar:

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

La notación de corte toma la forma [start:stop:step]. En este caso, omitimos el start y stop posiciones ya que queremos toda la cadena. También usamos step = -1lo que significa, “paso repetidamente de derecha a izquierda por 1 carácter”.

  • Esta solución (y la mayoría de las otras respuestas) no funciona para todos Unicode, incluso cuando se aplica a las cadenas Unicode de Python. Por ejemplo, "🇬🇧"[::-1] rendimientos "🇧🇬". La solución adecuada es reversed_string = "".join(list(grapheme.graphemes(input_string))[::-1]). Vea la respuesta de Martin a continuación.

    – amaurea

    5 de abril de 2021 a las 2:00

Avatar de usuario de Alex Martelli
alex martelli

@Paolo’s s[::-1] es el más rápido; un enfoque más lento (quizás más legible, pero eso es discutible) es ''.join(reversed(s)).

  • Esto es aproximadamente 3 veces más lento.

    – uno mismo

    06/10/2017 a las 15:09


  • ¡Y un comentario rápido para decir lo que hace lo explicará mejor que usar esta versión más lenta!

    – Tom Burrows

    2 de noviembre de 2017 a las 22:04

  • es más lento porque join posee para construir la lista de todos modos para poder obtener el tamaño. ''.join(list(reversed(s))) puede ser un poco más rápido.

    – Jean-François Fabre

    11 de diciembre de 2017 a las 21:34

  • ¿Tienes alguna información sobre por qué? [::-1] es el mas rapido? Me gustaría sumergirme más profundo.

    – curtidor

    30 oct 2019 a las 17:03

  • @Curtidor [::-1] es más rápido porque no llama a ninguna función externa, sino que usa el corte, que está altamente optimizado en python. ”.join(list(reversed(s))) hace 3 llamadas de función.

    – hd1

    27 de abril de 2020 a las 13:51

Rusia debe eliminar el avatar de usuario de Putin
Rusia debe sacar a Putin

¿Cuál es la mejor manera de implementar una función inversa para cadenas?

Mi propia experiencia con esta pregunta es académica. Sin embargo, si es un profesional que busca la respuesta rápida, use un segmento que vaya paso a paso. -1:

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

o más legible (pero más lento debido a las búsquedas de nombres de métodos y al hecho de que join forma una lista cuando se le da un iterador), str.join:

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

o para legibilidad y reutilización, coloque el segmento en una función

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

y luego:

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

explicación más larga

Si está interesado en la exposición académica, siga leyendo.

No hay una función inversa incorporada en el objeto str de Python.

Aquí hay un par de cosas sobre las cadenas de Python que debe saber:

  1. en Python, las cadenas son inmutables. Cambiar una cadena no modifica la cadena. Crea uno nuevo.

  2. Las cadenas son rebanables. Cortar una cadena le da una nueva cadena desde un punto de la cadena, hacia atrás o hacia adelante, a otro punto, en incrementos dados. Toman notación de segmento o un objeto de segmento en un subíndice:

    string[subscript]
    

El subíndice crea un segmento al incluir dos puntos entre las llaves:

    string[start:stop:step]

Para crear una división fuera de las llaves, deberá crear un objeto de división:

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

Un enfoque legible:

Tiempo ''.join(reversed('foo')) es legible, requiere llamar a un método de cadena, str.join, en otra función llamada, que puede ser relativamente lenta. Pongamos esto en una función – volveremos a ella:

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

Enfoque más eficaz:

Mucho más rápido es usar un corte inverso:

'foo'[::-1]

Pero, ¿cómo podemos hacer que esto sea más legible y comprensible para alguien menos familiarizado con los cortes o la intención del autor original? Vamos a crear un objeto de segmento fuera de la notación de subíndice, darle un nombre descriptivo y pasarlo a la notación de subíndice.

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

Implementar como función

Para implementar esto como una función, creo que es semánticamente lo suficientemente claro como para usar simplemente un nombre descriptivo:

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

Y el uso es simplemente:

reversed_string('foo')

Lo que tu maestro probablemente quiere:

Si tiene un instructor, probablemente querrá que comience con una cadena vacía y cree una nueva cadena a partir de la anterior. Puede hacer esto con sintaxis pura y literales usando un bucle 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

Esto es teóricamente malo porque, recuerda, las cadenas son inmutables – así que cada vez que parezca que estás agregando un personaje a tu new_string, ¡teóricamente está creando una nueva cadena cada vez! Sin embargo, CPython sabe cómo optimizar esto en ciertos casos, de los cuales este caso trivial es uno.

Mejores prácticas

En teoría, mejor es recopilar sus subcadenas en una lista y unirlas más tarde:

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)

Sin embargo, como veremos en los tiempos a continuación para CPython, en realidad lleva más tiempo, porque CPython puede optimizar la concatenación de cadenas.

Horarios

Aquí están los horarios:

>>> 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 optimiza la concatenación de cadenas, mientras que otras implementaciones podría no:

… no confíe en la implementación eficiente de CPython de la concatenación de cadenas en el lugar para declaraciones en la forma a += bo a = a + b . Esta optimización es frágil incluso en CPython (solo funciona para algunos tipos) y no está presente en absoluto en implementaciones que no usan refcounting. En las partes sensibles al rendimiento de la biblioteca, se debe usar el formulario ”.join() en su lugar. Esto asegurará que la concatenación ocurra en tiempo lineal a través de varias implementaciones.

  • Me encanta esta respuesta, explicaciones sobre optimizaciones, legibilidad frente a optimización, consejos sobre lo que quiere el profesor. No estoy seguro acerca de la sección de mejores prácticas con el while y decrementando el índice, aunque quizás esto sea menos legible: for i in range(len(a_string)-1, -1, -1): . Sobre todo, me encanta que la cadena de ejemplo que ha elegido es el único caso en el que nunca necesitaría invertirla, y no sería capaz de saber si lo hizo 🙂

    – Davos

    14 de agosto de 2019 a las 15:38


avatar de usuario de dreftymac
dreftymac

Respuesta rápida (TL;DR)

Ejemplo

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

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

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

Respuesta detallada

Antecedentes

Esta respuesta se proporciona para abordar la siguiente inquietud de @odigity:

Guau. Al principio me horroricé con la solución que propuso Paolo, pero eso pasó a un segundo plano frente al horror que sentí al leer el primer comentario: “Eso es muy pitónico. ¡Buen trabajo!” Estoy tan perturbado que una comunidad tan brillante piense que usar métodos tan crípticos para algo tan básico es una buena idea. ¿Por qué no es solo s.reverse()?

Problema

  • Contexto
    • Pitón 2.x
    • Pitón 3.x
  • Guión:
    • El desarrollador quiere transformar una cadena.
    • La transformación es invertir el orden de todos los personajes.

Solución

Trampas

  • El desarrollador podría esperar algo como string.reverse()
  • Es posible que la solución idiomática nativa (también conocida como “pythonic”) no sea legible para los desarrolladores más nuevos
  • El desarrollador puede verse tentado a implementar su propia versión de string.reverse() para evitar la notación de corte.
  • La salida de la notación de corte puede ser contraria a la intuición en algunos casos:
    • ver por ejemplo, ejemplo02
      • print 'coup_ate_grouping'[-4:] ## => 'ping'
      • comparado con
      • print 'coup_ate_grouping'[-4:-1] ## => 'pin'
      • comparado con
      • print 'coup_ate_grouping'[-1] ## => 'g'
    • los diferentes resultados de la indexación en [-1] puede despistar a algunos desarrolladores

Razón fundamental

Python tiene una circunstancia especial a tener en cuenta: una cadena es un iterable escribe.

Una razón para excluir un string.reverse() El método es dar a los desarrolladores de Python un incentivo para aprovechar el poder de esta circunstancia especial.

En términos simplificados, esto simplemente significa que cada carácter individual en una cadena se puede operar fácilmente como parte de una disposición secuencial de elementos, al igual que las matrices en otros lenguajes de programación.

Para comprender cómo funciona esto, revisar el ejemplo 02 puede proporcionar una buena descripción general.

Ejemplo02

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

Conclusión

Él carga cognitiva asociado con la comprensión de cómo funciona la notación de corte en python puede ser demasiado para algunos usuarios y desarrolladores que no desean invertir mucho tiempo en aprender el lenguaje.

Sin embargo, una vez que se comprendan los principios básicos, el poder de este enfoque sobre los métodos de manipulación de cadenas fijas puede ser bastante favorable.

Para aquellos que piensan lo contrario, existen enfoques alternativos, como funciones lambda, iteradores o declaraciones de funciones únicas simples.

Si lo desea, un desarrollador puede implementar su propio método string.reverse(), sin embargo, es bueno comprender la lógica detrás de este aspecto de python.

Ver también

  • enfoque simple alternativo
  • enfoque simple alternativo
  • explicación alternativa de la notación de corte

Avatar de usuario de Martin Thoma
Martín Tomas

Esta respuesta es un poco más larga y contiene 3 secciones: Puntos de referencia de las soluciones existentes, por qué la mayoría de las soluciones aquí son incorrectas, mi solución.

Las respuestas existentes solo son correctas si se ignoran los modificadores Unicode / grupos de grafemas. Me ocuparé de eso más adelante, pero primero eche un vistazo a la velocidad de algunos algoritmos de inversión:

ingrese la descripción de la imagen aquí

NOTA: Tengo lo que llamé list_comprehension debe ser llamado 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

ingrese la descripción de la imagen aquí

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

Puede ver que el tiempo para la comprensión de la lista (reversed = string[::-1]) es en todos los casos, con mucho, el más bajo (incluso después de corregir mi error tipográfico).

Inversión de cuerdas

Si realmente desea invertir una cadena en el sentido común, es MUCHO más complicado. Por ejemplo, tome la siguiente cadena (dedo marrón apuntando a la izquierda, dedo amarillo apuntando hacia arriba). Esos son dos grafemas, pero 3 puntos de código Unicode. El adicional es un modificador de piel.

example = "👈🏾👆"

Pero si lo invierte con cualquiera de los métodos dados, obtiene dedo marrón apuntando hacia arriba, dedo amarillo apuntando a la izquierda. La razón de esto es que el modificador de color “marrón” todavía está en el medio y se aplica a lo que sea que esté antes. Entonces tenemos

  • U: dedo apuntando hacia arriba
  • M: modificador marrón
  • L: dedo apuntando a la izquierda

y

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

Clústeres de grafemas Unicode son un poco más complicados que solo puntos de código modificador. Afortunadamente, hay una biblioteca para manejar grafemas:

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

y por lo tanto la respuesta correcta sería

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

que también es, con mucho, el más lento:

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

El código

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

  • Gracias por mostrar la inversión adecuada de cadenas con reconocimiento de grafemas. Para casi cualquier propósito realista, todas las demás respuestas aquí son incorrectas. Sin embargo, es una pena que tengas menos del 1% de los votos de la respuesta más popular.

    – amaurea

    5 de abril de 2021 a las 1:50

  • Sin embargo, es un poco desafortunado que su solución esté semioculta en medio de muchas cosas de evaluación comparativa. Lo pondría mucho antes y de manera más prominente, y tal vez mostraría explícitamente cómo las soluciones simples que otros dan salen mal (lo describe pero no lo muestra). Los emojis de bandera también son buenos ejemplos de esto.

    – amaurea

    5 de abril de 2021 a las 1:52

  • Es bueno ver que vale la pena leer no solo las respuestas al principio. Por cierto: ¿No sería mejor mostrar las estadísticas (incluida la versión compatible con grafemas) al final?

    – Lobo

    11 de septiembre a las 17:38


  • ¿Por qué tu list_comprehension ¿La función no contiene listas de comprensión?

    – khelwood

    11 de diciembre a las 7:47

  • @khelwood Tienes razón, “rebanar” hubiera sido un mejor nombre. Soy demasiado perezoso para ajustar todos los gráficos ahora, sin embargo … tal vez una actualización “3.11” sería interesante

    – Martín Tomas

    12 de diciembre a las 13:23

1. usando la notación de corte

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

2. usando la función invertida()

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

3. usando recursividad

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

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

  • Gracias por mostrar la inversión adecuada de cadenas con reconocimiento de grafemas. Para casi cualquier propósito realista, todas las demás respuestas aquí son incorrectas. Sin embargo, es una pena que tengas menos del 1% de los votos de la respuesta más popular.

    – amaurea

    5 de abril de 2021 a las 1:50

  • Sin embargo, es un poco desafortunado que su solución esté semioculta en medio de muchas cosas de evaluación comparativa. Lo pondría mucho antes y de manera más prominente, y tal vez mostraría explícitamente cómo las soluciones simples que otros dan salen mal (lo describe pero no lo muestra). Los emojis de bandera también son buenos ejemplos de esto.

    – amaurea

    5 de abril de 2021 a las 1:52

  • Es bueno ver que vale la pena leer no solo las respuestas al principio. Por cierto: ¿No sería mejor mostrar las estadísticas (incluida la versión compatible con grafemas) al final?

    – Lobo

    11 de septiembre a las 17:38


  • ¿Por qué tu list_comprehension ¿La función no contiene listas de comprensión?

    – khelwood

    11 de diciembre a las 7:47

  • @khelwood Tienes razón, “rebanar” hubiera sido un mejor nombre. Soy demasiado perezoso para ajustar todos los gráficos ahora, sin embargo … tal vez una actualización “3.11” sería interesante

    – Martín Tomas

    12 de diciembre a las 13:23

Avatar de usuario de pX0r
pX0r

Una forma menos desconcertante de verlo sería:

string = 'happy'
print(string)

‘contento’

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

‘ypá’

En Inglés [-1::-1] se lee como:

“Comenzando en -1, avanza hasta el final, dando pasos de -1”

  • Él -1 aunque todavía es innecesario.

    –Eric Duminil

    13/03/2019 a las 16:30

  • @EricDuminil si quieres entender que es necesario en mi opinión

    –Kyoko Sasagava

    2 de agosto de 2021 a las 8:20

¿Ha sido útil esta solución?