1705

There is no built in reverse method for Python's str object. How can I reverse a string?

7
  • 6
    Most of the answers do not deal with Unicode graphemes. For example "🇬🇧"[::-1] == "🇧🇬" which is likely not desired. If you want an answer that deals with that, you need to look at my answer. Look for "Unicode Modifiers / grapheme clusters" Commented Dec 10, 2023 at 11:59
  • 1
    Everyone loves to ask about this, but they're much quieter when asked for good reasons why reversing a string would solve a practical, real-world problem. Commented Dec 23, 2023 at 7:12
  • 1
    @KarlKnechtel The practical answer is: This is an interview question. If you want to get the job, you need to solve it. Could there be better interview questions? Maybe. But a candidate who starts meta-discussions instead of solving the task will likely not get the job. And, as you can see with my answer, candidates can show a deeper understanding with this kind of question. Commented Mar 14, 2024 at 7:33
  • "But a candidate who starts meta-discussions instead of solving the task will likely not get the job." - Really? If I were an employer, I would strongly prefer candidates who understand the ambiguity of the problem description, over those who naively assume that putting Unicode code points in reverse order will create a sensible result. It's not 1990 any more. Commented Mar 14, 2024 at 20:24
  • 2
    @BillEvansatMariposa: "abc.def.ghi".rindex(".") Commented May 30 at 9:07

14 Answers 14

3142

Using slicing:

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

Slice notation takes the form [start:stop:step]. In this case, we omit the start and stop positions since we want the whole string. We also use step = -1, which means, "repeatedly step from right to left by 1 character".

Sign up to request clarification or add additional context in comments.

14 Comments

This solution (and most of the other answers) does not work for all of unicode, even when applied to Python unicode strings. For example, "🇬🇧"[::-1] yields "🇧🇬". The proper solution is reversed_string = "".join(list(grapheme.graphemes(input_string))[::-1]). See Martin's answer below.
@amaurea What is wrong with that BG output?
@lmat-ReinstateMonica: the reverse of the grapheme "🇬🇧" should be simply "🇬🇧". Not "🇧🇬". (Just like how the reverse of "a" should be "a", not something else).
@smci what I mean is: the reason that lmat could ask "What is wrong with that BG output?" is because on lmat's screen, the single graphemes for the flag of Great Britain and the flag of Bulgaria did not appear, but instead fancy symbols representing the Unicode regional indicator symbols corresponding to the letters B and G, in either order. Displayed that way, the result from [::-1] makes sense, since two apparent separate (albeit strange) graphemes simply swap position. In short: "platform/console/shell X displays Unicode" is not a binary property.
@smci yes, I understand perfectly well why the proposed code is wrong. But in 2024, you responded to someone who asked "What is wrong with that BG output?", and your explanation just re-iterated that the flag shouldn't change, apparently unaware that the other person probably wasn't seeing flags. (And Python does not "assume every character is 7/8-bit long"; ASCII cannot represent the regional indicator symbols.) On some systems, if you put the🇧 and 🇬 symbols adjacent to each other, they won't be merged into a flag grapheme. But such a system is still displaying Unicode.
|
325

What is the best way of implementing a reverse function for strings?

My own experience with this question is academic. However, if you're a pro looking for the quick answer, use a slice that steps by -1:

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

or more readably (but slower due to the method name lookups and the fact that join forms a list when given an iterator), str.join:

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

or for readability and reusability, put the slice in a function

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

and then:

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

Longer explanation

If you're interested in the academic exposition, please keep reading.

There is no built-in reverse function in Python's str object.

Here is a couple of things about Python's strings you should know:

  1. In Python, strings are immutable. Changing a string does not modify the string. It creates a new one.

  2. Strings are sliceable. Slicing a string gives you a new string from one point in the string, backwards or forwards, to another point, by given increments. They take slice notation or a slice object in a subscript:

    string[subscript]
    

The subscript creates a slice by including a colon within the braces:

    string[start:stop:step]

To create a slice outside of the braces, you'll need to create a slice object:

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

A readable approach:

While ''.join(reversed('foo')) is readable, it requires calling a string method, str.join, on another called function, which can be rather relatively slow. Let's put this in a function - we'll come back to it:

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

Most performant approach:

Much faster is using a reverse slice:

'foo'[::-1]

But how can we make this more readable and understandable to someone less familiar with slices or the intent of the original author? Let's create a slice object outside of the subscript notation, give it a descriptive name, and pass it to the subscript notation.

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

Implement as Function

To actually implement this as a function, I think it is semantically clear enough to simply use a descriptive name:

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

And usage is simply:

reversed_string('foo')

What your teacher probably wants:

If you have an instructor, they probably want you to start with an empty string, and build up a new string from the old one. You can do this with pure syntax and literals using a while loop:

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

This is theoretically bad because, remember, strings are immutable - so every time where it looks like you're appending a character onto your new_string, it's theoretically creating a new string every time! However, CPython knows how to optimize this in certain cases, of which this trivial case is one.

Best Practice

Theoretically better is to collect your substrings in a list, and join them later:

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)

However, as we will see in the timings below for CPython, this actually takes longer, because CPython can optimize the string concatenation.

Timings

Here are the 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 optimizes string concatenation, whereas other implementations may not:

... do not rely on CPython's efficient implementation of in-place string concatenation for statements in the form a += b or a = a + b . This optimization is fragile even in CPython (it only works for some types) and isn't present at all in implementations that don't use refcounting. In performance sensitive parts of the library, the ''.join() form should be used instead. This will ensure that concatenation occurs in linear time across various implementations.

1 Comment

I love this answer, explanations about optimizations, readability vs optimization, tips on what the teacher wants. I'm not sure about the best practice section with the while and decrementing the index, although perhaps this is less readable: for i in range(len(a_string)-1, -1, -1): . Most of all I love that the example string you've chosen is the one case where you would never need to reverse it, and wouldn't be able to tell if you had :)
310

@Paolo's s[::-1] is fastest; a slower approach (maybe more readable, but that's debatable) is ''.join(reversed(s)).

5 Comments

This is about 3 times slower.
And a quick comment to say what it does will explain it better than using this slower version!
it's slower because join has to build the list anyway to be able to get the size. ''.join(list(reversed(s))) may be slightly faster.
Do you have any info on why [::-1] is fastest? I'd like to dive deeper.
@Tanner [::-1] is fastest because it does not call any external functions, rather it's using slicing, which is highly-optimized in python. ''.join(list(reversed(s))) makes 3 function calls.
76
+150

This answer is a bit longer and contains 3 sections: Benchmarks of existing solutions, why most solutions here are wrong, my solution.

The existing answers are only correct if Unicode Modifiers / grapheme clusters are ignored. I'll deal with that later, but first have a look at the speed of some reversal algorithms:

enter image description here

NOTE: I've what I called list_comprehension should be called slicing

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

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

You can see that the time for the slicing (reversed = string[::-1]) is in all cases by far the lowest (even after fixing my typo).

String Reversal

If you really want to reverse a string in the common sense, it is WAY more complicated. For example, take the following string (brown finger pointing left, yellow finger pointing up). Those are two graphemes, but 3 unicode code points. The additional one is a skin modifier.

example = "👈🏾👆"

But if you reverse it with any of the given methods, you get brown finger pointing up, yellow finger pointing left. The reason for this is that the "brown" color modifier is still in the middle and gets applied to whatever is before it. So we have

  • U: finger pointing up
  • M: brown modifier
  • L: finger pointing left

and

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

Unicode Grapheme Clusters are a bit more complicated than just modifier code points. Luckily, there is a library for handling graphemes:

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

and hence the correct answer would be

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

which also is by far the slowest:

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

The 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 = [(slicing, 'slicing', 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 slicing(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()

8 Comments

Thanks for showing the proper grapheme-aware string reversal. For almost any realistic purpose all the other answers here are wrong. Too bad you have less than 1% of the votes of the most popular answer, though.
It's a bit unfortunate that your solution is semi-hidden in the middle of lots of benchmarking stuff, though. I would put it much earlier and more prominently, and maybe show explicitly how the simple solutions the others give go wrong (you describe it but don't show it). Flag emojis also make good examples of this.
Good to see that it's worth reading not only the answers at the beginning. BTW: Wouldn't it be better to bring the stats (including your grapheme-aware version) at the end?
@khelwood You're right, "slicing" would have been a better name. I'm too lazy to adjust all the graphics now, though ... maybe a "3.11" update would be interesting though
Agreed with @amaurea that benchmarks should not be more prominent than pointing out the correct solution. And that reversing Ukraine to get Australia would make for a much snappier example.
|
47

Quick Answer (TL;DR)

Example

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

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

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

Detailed Answer

Background

This answer is provided to address the following concern from @odigity:

Wow. I was horrified at first by the solution Paolo proposed, but that took a back seat to the horror I felt upon reading the first comment: "That's very pythonic. Good job!" I'm so disturbed that such a bright community thinks using such cryptic methods for something so basic is a good idea. Why isn't it just s.reverse()?

Problem

  • Context
    • Python 2.x
    • Python 3.x
  • Scenario:
    • Developer wants to transform a string
    • Transformation is to reverse order of all the characters

Solution

Pitfalls

  • Developer might expect something like string.reverse()
  • The native idiomatic (aka "pythonic") solution may not be readable to newer developers
  • Developer may be tempted to implement his or her own version of string.reverse() to avoid slice notation.
  • The output of slice notation may be counter-intuitive in some cases:
    • see e.g., example02
      • print 'coup_ate_grouping'[-4:] ## => 'ping'
      • compared to
      • print 'coup_ate_grouping'[-4:-1] ## => 'pin'
      • compared to
      • print 'coup_ate_grouping'[-1] ## => 'g'
    • the different outcomes of indexing on [-1] may throw some developers off

Rationale

Python has a special circumstance to be aware of: a string is an iterable type.

One rationale for excluding a string.reverse() method is to give python developers incentive to leverage the power of this special circumstance.

In simplified terms, this simply means each individual character in a string can be easily operated on as a part of a sequential arrangement of elements, just like arrays in other programming languages.

To understand how this works, reviewing example02 can provide a good overview.

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'

Conclusion

The cognitive load associated with understanding how slice notation works in python may indeed be too much for some adopters and developers who do not wish to invest much time in learning the language.

Nevertheless, once the basic principles are understood, the power of this approach over fixed string manipulation methods can be quite favorable.

For those who think otherwise, there are alternate approaches, such as lambda functions, iterators, or simple one-off function declarations.

If desired, a developer can implement her own string.reverse() method, however it is good to understand the rationale behind this aspect of python.

See also

Comments

12

A lesser perplexing way to look at it would be:

string = 'happy'
print(string)

'happy'

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

'yppah'

In English [-1::-1] reads as:

"Starting at -1, go all the way, taking steps of -1"

2 Comments

The -1 is still unneeded, though.
@EricDuminil if you want to understand it is needed in my opinion
10

Reverse a string in python without using reversed() or [::-1]

def reverse(test):
    n = len(test)
    x=""
    for i in range(n-1,-1,-1):
        x += test[i]
    return x

Comments

5

This is also an interesting way:

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

or similar:

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

Another more 'exotic' way using bytearray which supports .reverse()

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

will produce:

'!siht esreveR'

Comments

3
def reverse(input):
    return reduce(lambda x,y : y+x, input)

2 Comments

I clicked upvote, because I like this lambda expression. Unfortunately, it's the least efficient solution from all listed above (test: Gist palindrome.py )
This is a terrible solution, needlessly inefficient
1

Here is a no fancy one:

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 Comment

print (reverse("hello, world!")) . put brackets around print statement
1

To solve this in programing way for interview

def reverse_a_string(string: str) -> str:
    """
    This method is used to reverse a string.
    Args:
        string: a string to reverse

    Returns: a reversed string
    """
    if type(string) != str:
        raise TypeError("{0} This not a string, Please provide a string!".format(type(string)))
    string_place_holder = ""
    start = 0
    end = len(string) - 1
    if end >= 1:
        while start <= end:
            string_place_holder = string_place_holder + string[end]
            end -= 1
        return string_place_holder
    else:
        return string


a = "hello world"
rev = reverse_a_string(a)
print(rev)

Output:

dlrow olleh

Comments

0
 a=input()
 print(a[::-1])

The above code recieves the input from the user and prints an output that is equal to the reverse of the input by adding [::-1].

OUTPUT:

>>> Happy 
>>> yppaH

But when it comes to the case of sentences, view the code output below:

>>> Have a happy day
>>> yad yppah a evaH

But if you want only the characters of the string to be reversed and not the sequence of string, try this:

a=input().split() #Splits the input on the basis of space (" ")
for b in a: #declares that var (b) is any value in the list (a)
    print(b[::-1], end=" ") #End declares to print the character in its quotes (" ") without a new line.

In the above code in line 2 in I said that ** variable b is any value in the list (a)** I said var a to be a list because when you use split in an input the variable of the input becomes a list. Also remember that split can't be used in the case of int(input())

OUTPUT:

>>> Have a happy day
>>> evaH a yppah yad

If we don't add end(" ") in the above code then it will print like the following:

>>> Have a happy day
>>> evaH
>>> a
>>> yppah
>>> yad

Below is an example to understand end():

CODE:

for i in range(1,6):
     print(i) #Without end()

OUTPUT:

>>> 1
>>> 2
>>> 3
>>> 4
>>> 5

Now code with end():

for i in range(1,6):
    print(i, end=" || ")

OUTPUT:

>>> 1 || 2 || 3 || 4 || 5 ||

Comments

-1

Recursive method:

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

example:

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

Comments

-2

Using For loop

name = 'Python'

strnew = ""
for i in name:
    strnew = i+strnew
print("After reverse string " +strnew)

1 Comment

This is not efficient way appending a char at the beginning you are technically copying rest of the elements, Instead you could iterate in reverse.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.