71

What is the best way to loop over a python string backwards?

The following seems a little awkward for all the need of -1 offset:

string = "trick or treat"
for i in range(len(string)-1, 0-1, -1):
    print string[i]

The following seems more succinct, but is it actually generate a reversed string so that there is a minor performance penalty?

string = "trick or treat"
for c in string[::-1]:
    print c
ivan_pozdeev
  • 33,874
  • 19
  • 107
  • 152
clwen
  • 20,004
  • 31
  • 77
  • 94
  • 2
    Is there any specific reason why you wrote 0-1 as the second parameter for range? When I tried to run this it works for simply -1. I was just curious to know whether it is just a writing convention or am I missing something. – newbie Mar 17 '14 at 20:20
  • @newbie I guess it's `0` that was later fixed to `-1`. – ivan_pozdeev Aug 09 '15 at 10:28

12 Answers12

115

Try the reversed builtin:

for c in reversed(string):
     print c

The reversed() call will make an iterator rather than copying the entire string.

PEP 322 details the motivation for reversed() and its advantages over other approaches.

Raymond Hettinger
  • 216,523
  • 63
  • 388
  • 485
10

EDIT: It has been quite some time since I wrote this answer. It is not a very pythonic or even efficient way to loop over a string backwards. It does show how one could utilize range and negative step values to build a value by looping through a string and adding elements in off the end of the string to the front of the new value. But this is error prone and the builtin function reversed is a much better approach. For those readers attempting to understand how reversed is implemented, take a look at the PEP, number 322, to get an understanding of the how and why. The function checks whether the argument is iterable and then yields the last element of a list until there are no more elements to yield. From the PEP:

[reversed] makes a reverse iterator over sequence objects that support getitem() and len().

So to reverse a string, consume the iterator until it is exhausted. Without using the builtin, it might look something like,

def reverse_string(x: str) -> str:
i = len(x)
while i > 0:
    i -= 1
    yield x[i]
    

Consume the iterator either by looping, eg

for element in (reverse_string('abc')): 
    print(element)

Or calling a constructor like:

cba = list(reverse_string('abc'))

The reverse_string code is almost identical to the PEP with a check removed for simplicity's sake. In practice, use the builtin.

ORIGNAL ANSWER:

Here is a way to reverse a string without utilizing the built in features such as reversed. Negative step values traverse backwards.

def reverse(text):
    rev = ''
    for i in range(len(text), 0, -1):
        rev += text[i-1]
    return rev
Nathan
  • 3,082
  • 1
  • 27
  • 42
  • 1
    Please check whether the above program works or it will give indentation error – cyborg May 19 '15 at 15:36
  • I have updated the answer to correct the white space error. This currently works in the Python 2.7.7 shell. – Nathan May 20 '15 at 18:02
  • -1: much worse than a slice (`text[-1:-1:-1]`). For a 100k string, `timeit` shows ~10ms worst for slice and 177ms average for the function. – ivan_pozdeev Aug 09 '15 at 10:21
  • 1
    Good point. I am not claiming to offer the most optimal solution. My solution is verbose and great for beginners attempting to learn the logic behind some basic tasks. You should add your slice solution as an answer. – Nathan Aug 09 '15 at 13:40
  • @Nathan then your actions promote exactly the opposite goal to the one you stated: for beginners, it's _especially_ critical to learn the best ways to do things right away. It's unnecessary to add an answer on the slice as the OP has already mentioned it. – ivan_pozdeev Aug 09 '15 at 15:23
5

Yes, the second syntax shortcut creates an intermediate string and has an associated performance penalty.

The first version is better written as:

for index, char in enumerate(reversed(s)):
   print "pos %d: %s" % (index, char)

Which is easy to comprehend. Neither reversed nor enumerate` need to make a copy of the string.

Also be careful about using string as a variable name, as it is also the name of a module in the standard library.

Raymond Hettinger
  • 216,523
  • 63
  • 388
  • 485
Kenan Banks
  • 207,056
  • 34
  • 155
  • 173
  • 3
    I don't like this answer because the use of enumerate() makes the answer harder to understand. This is doubly true because the reported index is the new position, not the original position. +1 on the recommendation not to use "string" as a variable name. – Raymond Hettinger Nov 01 '11 at 01:16
  • I'm sure the shorter question will win. Sometimes I like to over-explain simple answers. – Kenan Banks Nov 01 '11 at 01:21
  • Rolled back to your previous version. The last change broke the code -- reversed works with sequences but not with an enumerate object. – Raymond Hettinger Nov 01 '11 at 03:08
5

reversed takes an iterable and and returns an iterator that moves backwards. string[::-1] is fine, but it creates a new, reversed string instead. If you just want to iterate, then this will probably better:

for c in reversed(string):
    print c

If you want to use the reversed string afterwards, creating it once will be better.

Cat Plus Plus
  • 125,936
  • 27
  • 200
  • 224
4
 string = "trick or treat"
 for c in string[::-1]:
     print c

I would use that. It is probably quite fast although there may be a slightly better way (but I doubt it).

EDIT: Actually, with a second test using a program I hacked together, reversed is probably the way to go.

 ==== Results ====
Sample 1: 0.0225071907043 # Using a for loop
Sample 2: 0.0100858211517 # Using reversed
AJ00200
  • 16,327
  • 7
  • 23
  • 21
3

Less code is usually faster in Python. Luckily, you don't have to guess:

python -mtimeit -s"s='x'*100000" "for x in s[::-1]: pass"
100 loops, best of 3: 1.99 msec per loop

python -mtimeit -s"s='x'*100000" "for x in reversed(s): pass"
1000 loops, best of 3: 1.97 msec per loop

python -mtimeit -s"s='x'*100000" "for i in xrange(len(s)-1, 0-1, -1): s[i]"
100 loops, best of 3: 4.95 msec per loop

So the shorter code is a bit faster, but it comes with a memory overhead.

Jochen Ritzel
  • 104,512
  • 31
  • 200
  • 194
  • Take care to localize range() and len() when making timings such as this; otherwise, the one-time global lookups tend to dominate the timings (especially when only 100 loops are run). – Raymond Hettinger Nov 01 '11 at 01:20
  • 3
    @Raymond Hettinger: Obviously iterating through 100000 chars will dominate the 2 global lookups. – Jochen Ritzel Nov 01 '11 at 01:43
  • *"So the shorter code is a bit faster"* - What do you mean? 1.99 isn't less than 1.97. – Kelly Bundy Mar 30 '22 at 05:28
2

Reverse a String in Python using For Loop

outputStr = ''
a = raw_input("Enter String: ")
for i in range(len(a), 0, -1):
    outputStr += a[i-1]
print outputStr
  • I feel this is the fastest as it does not require construction of a new String; as `reversed` would do. – Timothy C. Quinn Oct 29 '22 at 18:10
  • Note - This will not hit every string and will error out on the first. Use this insetad: `for i in range(len(a)-1, -1, -1):` I posted another answer with this example. – Timothy C. Quinn Oct 29 '22 at 18:35
2
string = "trick or treat"
for c in reversed(string):
    print c

Will do what I think you want. It uses an iterator. This should work with anything that has __reveresed__() or __len__() and __getitem__() implemented. __getitem__() would have to take int arguments starting at 0.

krousey
  • 1,728
  • 15
  • 22
1

Python 3 with enumerate and reversed methods:

string = "trick or treat"
for i, c in enumerate(reversed(string)):
    print(i, c)

You can use print(c) just for retrieving each character without the index.

ian0411
  • 4,115
  • 3
  • 25
  • 33
1

If you care about performance, its best to not use reversed as this will generate a new string and an unnecessary function call. Instead, just iterate backwards through the string.

Eg. This will find last newline in a string:

s = 'abcdefg\nxyz'
for i in range(len(s)-1, -1, -1):
    if s[i] == '\n':
        print('found \\n at %s' % i)
        break

# found \n at 7
Timothy C. Quinn
  • 3,739
  • 1
  • 35
  • 47
0
def reverse(text):
    x = ""
    for i in range(len(text)):
        x = x + text[len(text)-i-1]
    return x
josliber
  • 43,891
  • 12
  • 98
  • 133
praveen
  • 9
  • 1
  • Welcome to Stack Overflow! Please explain what your code does and why it will solve the problem. An answer that just contains code (even if it's working) usually wont help the OP to understand their problem. – SuperBiasedMan Aug 13 '15 at 08:43
-1

string reverse

def reverse(a_string):
    rev = ''
    for i in range(len(a_string)-1, -1, -1):
        rev = rev + a_string[i]
    return rev
print(reverse("This string should be reversed!"))

output is: !desrever eb dluohs gnirts sihT

  • 1
    Welcome to StackOverflow! Note that writing `string = string + other` within a loop is generally discouraged in Python since it causes unnecessary repetitive allocations. Almost always using `str.join` should be preferred, which only allocates memory for the new string once. You'll also note that your `reverse` function is essentially a less efficient version of the [`reversed`](https://docs.python.org/3/library/functions.html#reversed) builtin that is specialized to only work for strings. – Brian61354270 Apr 06 '20 at 16:33
  • Although this works as a string reverse function, this is indeed, like Brian said, a worse version of the reversed builtin, and doesn't actually directly answer the question, which was how to loop over a string backwards. – Anonymous1847 Apr 06 '20 at 17:23