1634

There is no built in reverse function for Python's str object. What is the best way of implementing this method?

If supplying a very concise answer, please elaborate on its efficiency. For example, whether the str object is converted to a different object, etc.

TylerH
  • 20,799
  • 66
  • 75
  • 101
oneself
  • 38,641
  • 34
  • 96
  • 120

20 Answers20

3051

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

Mateen Ulhaq
  • 24,552
  • 19
  • 101
  • 135
Paolo Bergantino
  • 480,997
  • 81
  • 517
  • 436
  • 9
    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 Apr 05 '21 at 02:00
308

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

zadrozny
  • 1,631
  • 3
  • 22
  • 27
Alex Martelli
  • 854,459
  • 170
  • 1,222
  • 1,395
  • 29
    This is about 3 times slower. – oneself Oct 06 '17 at 15:09
  • 4
    And a quick comment to say what it does will explain it better than using this slower version! – Tom Burrows Nov 02 '17 at 22:04
  • 5
    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. – Jean-François Fabre Dec 11 '17 at 21:34
  • 1
    Do you have any info on why [::-1] is fastest? I'd like to dive deeper. – Tanner Oct 30 '19 at 17:03
  • 4
    @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. – hd1 Apr 27 '20 at 13:51
307

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.

Russia Must Remove Putin
  • 374,368
  • 89
  • 403
  • 331
  • 18
    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 :) – Davos Aug 14 '19 at 15:38
52

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()
Martin Thoma
  • 124,992
  • 159
  • 614
  • 958
  • 6
    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. – amaurea Apr 05 '21 at 01:50
  • 4
    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. – amaurea Apr 05 '21 at 01:52
  • 2
    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? – Wolf Sep 11 '22 at 17:38
  • Why does your `list_comprehension` function contain no list comprehensions? – khelwood Dec 11 '22 at 07:47
  • @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 – Martin Thoma Dec 12 '22 at 13:23
  • @khelwood I've added a note for the moment :-) – Martin Thoma Dec 12 '22 at 13:26
  • 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. – user3840170 Mar 14 '23 at 10:35
  • I show how other solutions are wrong with the yellow/brown fingers. And I like the order of my answer :-) – Martin Thoma Mar 14 '23 at 15:49
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

dreftymac
  • 31,404
  • 26
  • 119
  • 182
15

1. using slice notation

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

2. using reversed() function

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

3. using recursion

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

    return s[-1] + rev_string(s[:-1])
Harry
  • 521
  • 7
  • 21
  • 3
    Gotta watch the recursion solution, if the string is decent length you'll run into `RecursionError: maximum recursion depth exceeded while calling a Python object`. Ex: `rev_string("abcdef"*1000)` – Adam Parkin Feb 21 '19 at 16:47
13

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"

pX0r
  • 1,472
  • 12
  • 14
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
akshaynagpal
  • 2,965
  • 30
  • 32
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'
gxpr
  • 836
  • 9
  • 12
4
def reverse(input):
    return reduce(lambda x,y : y+x, input)
josliber
  • 43,891
  • 12
  • 98
  • 133
Javier
  • 672
  • 5
  • 13
  • 3
    I clicked upvote, because I like this lambda expression. Unfortunately, it's the least efficient solution from all listed above (test: [Gist palindrome.py](https://gist.github.com/oskar-j/3784d80cd80031be223c) ) – oski86 Jul 24 '15 at 16:32
  • 1
    This is a terrible solution, needlessly inefficient – juanpa.arrivillaga Jul 23 '21 at 03:52
3

There are multiple ways to reverse a string in Python

Slicing Method

string = "python"
rev_string = string[::-1]
print(rev_string)

using reversed function

string = "python"
rev= reversed(string) 
rev_string = "".join(rev) 
print(rev_string)

Using Recursion

string = "python"
def reverse(string):
  if len(string)==0:
    return string
  else:
    return reverse(string[1:])+string[0]
print(reverse(string))

Using for Loop

string = "python"
rev_string =""
for s in string:
  rev_string = s+ rev_string
print(rev_string)

Using while Loop

string = "python"
rev_str =""
length = len(string)-1
while length >=0:
  rev_str += string[length]
  length -= 1
print(rev_str)
Buddhadeb Mondal
  • 187
  • 1
  • 10
2

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!")
an offer can't refuse
  • 4,245
  • 5
  • 30
  • 50
1
original = "string"

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

print(original)
print(rev_index)
print(''.join(rev_func))
JEX
  • 124
  • 1
  • 6
  • 2
    While this code may answer the question, it is better to explain how to solve the problem and provide the code as an example or reference. Code-only answers can be confusing and lack context. – Robert Columbia Dec 29 '18 at 11:11
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
0

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
matt
  • 607
  • 7
  • 20
0

Here is how we can reverse a string using for loop:

string = "hello,world"
for i in range(-1,-len(string)-1,-1):
    print (string[i], end=(" "))
Tomerikoo
  • 18,379
  • 16
  • 47
  • 61
Nitin
  • 1,280
  • 1
  • 13
  • 17
0
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"

This works by looping through a string and assigning its values in reverse order to another string.

Pika Supports Ukraine
  • 3,612
  • 10
  • 26
  • 42
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 ||
Code Carbonate
  • 640
  • 10
  • 18
0

Just as a different solution(because it's asked in interviews):

def reverse_checker(string):
    ns = ""
    for h in range(1,len(string)+1):
        ns  += string[-h]
    print(ns)

    if ns == string:
        return True
    else:
        return False
berkayln
  • 935
  • 1
  • 8
  • 14
0

Using For loop

name = 'Python'

strnew = ""
for i in name:
    strnew = i+strnew
print("After reverse string " +strnew)
Nilesh Shinde
  • 457
  • 5
  • 10