21

How do you reverse a Python string without omitting the start and end slice arguments?

word = "hello"
reversed_word = word[::-1]

I understand that this works, but how would I get the result by specifying the start and end indexes?

word = "hello"
reversed_word = word[?:?:-1]

It's hard to explain to students why word[::-1] reverses a string. It's better if I can give them logical reasoning rather than "it's the pythonic way".

The way I explain word[::1] is as follows: "You have not specified the start so it just starts from the start. You have not specified the end so it just goes until the end. Now the step is 1 so it just goes from the start to the end 1 character by 1." Now when my students see word[::-1] they are going to think "We have not specified the start or the end so it will go through the string -1 characters at a time?"

Russia Must Remove Putin
  • 374,368
  • 89
  • 403
  • 331
Ogen
  • 6,499
  • 7
  • 58
  • 124

7 Answers7

15

Some other ways to reverse a string:

word = "hello"
reversed_word1 = word[-1: :-1] 
reversed_word2 = word[len(word)-1: :-1]   
reversed_word3 = word[:-len(word)-1 :-1]    

One thing you should note about the slicing notation a[i:j:k] is that omitting i and j doesn't always mean that i will become 0 and j will become len(s). It depends upon the sign of k. By default k is +1.

  • If k is +ve then the default value of i is 0 (start from the beginning). If it is -ve then the default value of i is -1 (start from the end).
  • If k is +ve then the default value of j is len(s) (stop at the end). If it is -ve then the default value of j is -(len(s)+1) (stop at the beginning).

Now you can explain your students how Hello[::-1] prints olleH.

haccks
  • 104,019
  • 25
  • 176
  • 264
  • this would make even more sense if `-(len(s)+1)` could just be 0. Because that is also the beginning – Ogen Mar 28 '15 at 04:57
  • @Ogen; No. It is actually one past the `0`. `-len(s)` is equivalent to `0`. – haccks Mar 28 '15 at 05:01
13

Not quite sure why, but the following will return the reverse of word:

word = "hello"
word[len(word):-(len(word)+1):-1]

Or...

word = "hello"
word[len(word):-len(word)-1:-1]

Edit (Explanation):

From jedward's comment:

The middle parameter is the trickiest, but it's pretty straightforward once you realize (a) negative indices to slice start/stop indicate that you want to count "backwards" from the end of the string, and (b) the stop index is exclusive, so it will "count" up to but stop at/before the stop index. word[len(word):-len(word)-1:-1] is probably more clear.

In response to this comment:

The third value is actually the increment so you are telling Python that you want to start at the last letter then return all the (-1)st values up to the last one.

Here is an drawing (pulled together in a minute): enter image description here

The drawing shows that we can also use this instead:

word = "hello"
word[-1:-len(word)-1:-1] #-1 as the first
Community
  • 1
  • 1
jkd
  • 1,045
  • 1
  • 11
  • 27
  • That is weird... I have no idea why that works. Any explanation would be appreciated. – Ogen Mar 28 '15 at 01:03
  • 3
    The middle parameter is the trickiest, but it's pretty straightforward once you realize (a) negative indices to slice start/stop indicate that you want to count "backwards" from the end of the string, and (b) the stop index is *exclusive*, so it will "count" up to but stop at/before the stop index. `word[len(word):-len(word)-1:-1]` might be a bit clearer. – jedwards Mar 28 '15 at 01:03
  • 1
    @jedwards Can I quote you in my answer? – jkd Mar 28 '15 at 01:04
  • 2
    Don't even worry about quoting, just throw it in there :) – jedwards Mar 28 '15 at 01:05
  • I thought that the 3rd parameter, step, is responsible for the direction. A negative step would mean go backwards from the end, a positive step would mean go forwards from the start. That makes intuitive sense to me. – Ogen Mar 28 '15 at 01:07
  • @Ogen, you are correct that the step is responsible for the direction. But you also need to specify the stop parameter with a negative index as well. I'll try to illustrate why in a tiny comment box: You might try `word[len(word):0:-1]` which would get you close, but you'd lose the first letter (since stop is exclusive), so then you might try to do `word[len(word):-1:-1]` which makes sense, but negative indices for start/stop are interpreted differently (that is, from the end of the string) -- so using -1 as a stop actually means the last character -- which will result in an empty string... – jedwards Mar 28 '15 at 01:11
  • @Ogen (cont'd) so to "compensate" for the fact that python interprets negative start/stop indices as counting from the end of the string, you can't use `-1`, insead you have to use `-len(word)` -- but again, you'll be missing the first character of the original string / last of the reversed string. So to fix this additional twist, you have to subtract *an additional* 1. – jedwards Mar 28 '15 at 01:13
  • @jedwards Okay I understand now. I still think that only the step should be responsible for direction. The start and end parameters should just be used for the indices. I think this would be more clear and easier to understand but I guess it would affect other features of Python that I'm not aware of so that's okay. – Ogen Mar 28 '15 at 01:17
  • 2
    @Ogen, there's a good blog post somewhere by Guido van Rossum about how he decided on the slicing notation -- let me dig it up. EDIT: Here: [Why Python uses 0-based indexing](http://python-history.blogspot.com/2013/10/why-python-uses-0-based-indexing.html) – jedwards Mar 28 '15 at 01:18
  • 1
    @jedwards This maybe?: https://plus.google.com/115212051037621986145/posts/YTUxbXYZyfi – jkd Mar 28 '15 at 01:20
  • @jakekimds yep, it looks like that google+ post inspired his blog post the following day, but they're a bit different, so possibly both worth reviewing -- nice find. – jedwards Mar 28 '15 at 01:21
  • That post makes perfect sense. But it's only when you include the step parameter that the confusion arises because then the start, end and step parameters are all able to dictate the order of reading the string so it seems to conflict in my mind. That post doesn't show examples of string slicing with a non-default step parameter. But I understand that a sacrifice had to be made to keep the beautiful half-open intervals. – Ogen Mar 28 '15 at 01:25
  • @jedwards Can you explain why the stdin is empty when the last one is omitted (`word[len(word):-len(word)-1]`)? – jkd Mar 28 '15 at 01:27
  • I know that the step needs to be -1 but I just can't explain why. The point of this question was to find a logical way to explain it to students that are in an introductory computer science course but it's proving harder than I thought. – Ogen Mar 28 '15 at 01:34
  • I meant string not stdin in my last comment – jkd Mar 28 '15 at 01:34
  • @jakekimds sure, give me a few minutes and I'll throw together some code for an example. – jedwards Mar 28 '15 at 01:41
  • 2
    @jakekimds [Here is a gist](https://gist.github.com/jheiv/1d0efd0a0bc39e73bffd) that shows one way you could implement the actual slicing, given an iterable (in this case, a string) and a slice object. The last two slices are the ones that might interest you. The second to last slice is the answer you gave (`[len(text):-len(text)-1:-1]`), the last answer has the same start and stop, but no step. When step isn't provided, it defaults to 1 and the loop tries to count forward, but it's already *past* the end of the string starting at `len(text)`, so it stops immediately. – jedwards Mar 28 '15 at 02:40
  • @jakekimds That looks really nice and it makes sense. Thanks a lot :D – Ogen Mar 28 '15 at 03:01
4

If you do not specify a number for slice notation, Python will use None as a default. So, you can just explicitly write the None values:

>>> word = "hello"
>>> word[None:None:-1]
'olleh'
>>>
  • 5
    I am trying to understand why the answer wouldnt be `word[len(word):-1:-1]`. To me this reads: "start from the end of the word, and go backwards until you reach -1 and stop". But this doesn't work. – Ogen Mar 28 '15 at 00:57
  • Python doesn't knows that len(word) means "start from the end". Python thinks it is "Start from index that equals to some number". You need to read function definion in manual before using. There first number defined as "start" index. – Alexander R. Mar 28 '15 at 01:01
  • @ogen negative start or end mean to count from the end of the string, not to stop when it goes below 0. – Barmar Mar 28 '15 at 01:04
  • Sorry that was a typo. I meant `word[len(word) - 1:-1:-1]`. So now the start index is the last index and the end index is -1 because it's exclusive so the last index will be 0. – Ogen Mar 28 '15 at 01:10
  • `word[len(word)-1:-1:-1]` doesn't mean: "start from the end of the word, and go backwards until you reach -1 and stop". It means that: "Start from `-1` and stop when you reach at `-1`". As for string indices in python, `-1` is equivalent to `len[string]-1`. – haccks Mar 28 '15 at 04:58
4

From the Python 2 source, this is defined with ternary expressions:

defstart = *step < 0 ? length-1 : 0;
defstop = *step < 0 ? -1 : length;

So, when start and stop are not given,

If step is negative:

  • start is length - 1
  • stop is -1, (this is the C index, tricky to implement in Python, must be -length-1)

If step is positive:

  • start is 0
  • stop is length

So to answer this question:

How would I get the result by specifying the start and end indexes?

To specify this yourself, use e.g. the following (put into a function for reusability)

def my_slice(word, step):
    '''slice word with only step'''
    start = len(word)-1 if step < 0 else 0
    stop = -len(word)-1 if step < 0 else len(word)
    return word[start:stop:step]


word = "hello"
step = -1
my_slice(word, step)

returns

'olleh'
Russia Must Remove Putin
  • 374,368
  • 89
  • 403
  • 331
3
In [27]: word
Out[27]: 'hello'

In [28]: word[-1:-6:-1]
Out[28]: 'olleh'

Why this works:

In [46]: word
Out[46]: 'hello' 

In [47]: word[-1] # you want o to be your first value
Out[47]: 'o'

In [48]: word[-5] # you want h to be your last value
Out[48]: 'h'

In [49]: word[-1:-6:-1] # therefore ending point should be one
Out[49]: 'olleh'        # past your last value that is -6  
Akavall
  • 82,592
  • 51
  • 207
  • 251
2

The notation string[begin:end:increment] can be described with the following Python program:

def myreverse( string, begin, end, increment ):
  newstring = ""
  if begin >= 0 and end < 0:
    begin = -1
  for i in xrange(begin, end, increment):
    try:
      newstring += string[i]
    except IndexError:
      pass

  return newstring
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Sumurai8
  • 20,333
  • 11
  • 66
  • 100
1

Recursive approach:

def reverse(word):
    if len(word) <= 1:
        return word

    return reverse(word[1:]) + word[0]
Prashant Ghimire
  • 4,890
  • 3
  • 35
  • 46
  • That works nicely but I'm not looking for a recursive solution. There should be values that I can put into the slice that makes logical sense. – Ogen Mar 28 '15 at 01:00