Python Reverse a String
NOTE: Running Python 3.10.5 and IPython 8.22.2 in my environment when I created this post.
Reverse a String the Pythonic way
Reverse a String (str
) in Python is very, very… very simple.
Here we use ipython
to test this concept. We create the variable the text
and assigned the str
with value “Hello World” to it.
1
2
3
4
5
6
$ ipython
...
In [1]: text = "hello world"
In [2]:
Next, we make use of Python Slicing and the third argument that can eb used since Python 1.4
1
2
In [2]: text[::-1]
Out[2]: 'dlrow olleh'
See? That was veeery simple.
Reverse a String the non-Pythonic way
Now, let’s say that for some reason, we don’t want to use Python Slicing to reverse an string. In such case, we would need to reverse a string executing a series of steps… so, an algorithm. Luckily, this isn’t that hard.
We would need to track the initial and last letters of the str
1
2
3
In [3]: initial_index = 0
In [4]: last_index = len(text) -1
At first glance, and sticking to the "hello world"
we have a pointer to the first letter (h
) and the last letter (d
).
1
2
3
4
5
In [5]: text[initial_index]
Out[5]: 'h'
In [6]: text[last_index]
Out[6]: 'd'
Once we try to swap these letters directly in the text
variable, we get an error… an expected one since strings are inmutable in Python, meaning we cannot modify them.
1
2
3
4
5
6
7
In [7]: text[initial_index] = text[last_index]
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[7], line 1
----> 1 text[initial_index] = text[last_index]
TypeError: 'str' object does not support item assignment
What do we do? Based on the text
string, create a Python List. Here how to do it:
1
2
3
4
In [8]: text_list = list(text)
In [9]: text_list
Out[9]: ['h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd']
Great, we now have the same string, but as a Python List which we can modify. Let’s try again:
1
2
3
4
5
6
7
In [9]: text_list
Out[9]: ['h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd']
In [10]: text_list[initial_index] = text[last_index]
In [11]: text_list
Out[11]: ['d', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd']
We did not get any error this time, so that is good. However, we did not really swap the first and last letters, but we just copied the value of the last letter into the first one.
Let’s make test_list
have its original value.
1
2
3
4
In [12]: text_list = list(text)
In [13]: text_list
Out[13]: ['h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd']
The correct way to swap these in Python would be by relying on Python unpacking
1
2
3
4
In [15]: text_list[initial_index], text_list[last_index] = text_list[last_index], text_list[initial_index]
In [17]: text_list
Out[17]: ['d', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'h']
We really swaped the first and last letters now. So we keep doing this… with a loop of course.
What would be the correct steps?
- We should increase
initial_index
by 1 (so we have access to the second letter -e
) - And we should decrease
last_index
by 1 (so we have access to the one before the last letter -l
) - We swap them again.
We reapeat… until when? There are 2 cases to consider:
- The
str
has a odd number of characters, i.e.abc
- The
str
has an even number of characters, i.e.abcd
Now, let’s imagine we are reversing the first case, the odd number of charactesr in string abc
initial_index
points toa
last_index
points toc
- We swap them
- We increase
initial_index
by 1 and decreaselast_index
by 1 initial_index
points tob
last_index
points tob
- Since both point to
b
we should just not swap anymore.
So the condition to stop is that initial_index
should NOT be equal to last_index
.
Let’s go now over the seconds case, the even number of characters in string abcd
initial_index
points toa
last_index
points tod
- We swap them
- We increase
initial_index
by 1 and decreaselast_index
by 1 initial_index
points tob
last_index
points toc
- We swap them
- We increase
initial_index
by 1 and decreaselast_index
by 1 initial_index
points toc
last_index
points tob
- We already swaped them! so we should just stop now.
So the condition to stop is that initial_index
should NOT be larger than last_index
.
Considering both cases, initial_index
should NOT be equal and shold NOT be larger than last_index
. In other words, initial_index
should ALWAYS be LESS than last_index
We can do this in Python using a while
loop
1
2
3
4
5
6
7
8
9
10
In [18]: text_list = list(text)
In [19]: while(initial_index < last_index):
...: text_list[initial_index], text_list[last_index] = text_list[last_index], text_list[initial_index]
...: initial_index += 1
...: last_index -= 1
...:
In [20]: text_list
Out[20]: ['d', 'l', 'r', 'o', 'w', ' ', 'o', 'l', 'l', 'e', 'h']
This worked! Now if we want do get this result as a string, we should make use of the string method join
.
1
2
3
4
5
In [20]: text_list
Out[20]: ['d', 'l', 'r', 'o', 'w', ' ', 'o', 'l', 'l', 'e', 'h']
In [21]: "".join(text_list)
Out[21]: 'dlrow olleh'
Putting all together, here we have the code inside a function.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def reverse_string(text: str) -> str:
"""
text :str is the String to be reversed
Returns :str
A copy of the original string in reverse order
"""
if len(text) <= 1:
return text
text_list = list(text)
initial_index = 0
last_index = len(text) -1
while(initial_index < last_index):
text_list[initial_index], text_list[last_index] = text_list[last_index], text_list[initial_index]
initial_index += 1
last_index -= 1
return "".join(text_list)
Notice we added a couple of lines of code at the beginning of the function.
1
2
if len(text) <= 1:
return text
These are necessary in order to handle when the str
is either of size 1 (nothing to reverse) or size zero (basically an empty string).
Here we see it running in ipython
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
$ ipython
...
In [1]: def reverse_string(text: str) -> str:
...: """
...: text :str is the String to be reversed
...:
...: Returns :str
...: A copy of the original string in reverse order
...: """
...: if len(text) <= 1:
...: return text
...:
...: text_list = list(text)
...:
...: initial_index = 0
...: last_index = len(text) -1
...:
...: while(initial_index < last_index):
...: text_list[initial_index], text_list[last_index] = text_list[last_index], text_list[initial_index]
...:
...: initial_index += 1
...: last_index -= 1
...:
...: return "".join(text_list)
...:
In [2]: reverse_string("hello world")
Out[2]: 'dlrow olleh'
In [3]: reverse_string("abc")
Out[3]: 'cba'
In [4]: reverse_string("abcd")
Out[4]: 'dcba'
In [5]: reverse_string("a")
Out[5]: 'a'
In [6]: reverse_string("")
Out[6]: ''
Algorithm to detect a Palindrome
What is a Palindrome? According to Wikipedia: A palindrome is a word, number, phrase, or other sequence of symbols that reads the same backwards as forwards, such as madam or racecar. So, we can use most of the code in the reverse_string
algorithm to make it detect a word or phrase which is a Palindrome. Why? Because we already have code that traverses the word starting from the edges all the way to the middle.
1
2
3
4
5
6
7
8
initial_index = 0
last_index = len(text) -1
while(initial_index < last_index):
text_list[initial_index], text_list[last_index] = text_list[last_index], text_list[initial_index]
initial_index += 1
last_index -= 1
If we slighly modify it and instead of swapping the letter, it just verified they are the same letter, we would have the main code to check for Palindrome words.
1
2
3
4
5
6
7
8
9
initial_index = 0
last_index = len(text) -1
while(initial_index < last_index):
if text_list[initial_index] == text_list[last_index]
initial_index += 1
last_index -= 1
else:
# We should break as the word is NOT a palindrome
The full code would be:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def is_palindrome(text: str) -> bool:
"""
text :str is the String
Returns :bool
True if text is palindrome, False otherwise
"""
if len(text) <= 1:
return True
text_list = list(text)
initial_index = 0
last_index = len(text) -1
while(initial_index < last_index):
if text_list[initial_index] == text_list[last_index]:
initial_index += 1
last_index -= 1
else:
return False
return True
Note one more slight modifications, the function now returns a bool
either True
or False
as opposed to the reversed string
Testing the code manually in ipython
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
$ ipython
...
In [2]: def is_palindrome(text: str) -> bool:
...: """
...: text :str is the String
...:
...: Returns :bool
...: True if text is palindrome, False otherwise
...: """
...: if len(text) <= 1:
...: return True
...:
...: text_list = list(text)
...:
...: initial_index = 0
...: last_index = len(text) -1
...:
...: while(initial_index < last_index):
...: if text_list[initial_index] == text_list[last_index]:
...: initial_index += 1
...: last_index -= 1
...: else:
...: return False
...:
...: return True
...:
In [3]: is_palindrome("hello world")
Out[3]: False
In [4]: is_palindrome("abc")
Out[4]: False
In [5]: is_palindrome("abcba")
Out[5]: True
In [6]: is_palindrome("XabcbaX")
Out[6]: True
In [7]: is_palindrome("XabcbaX_")
Out[7]: False
In [8]: is_palindrome("a")
Out[8]: True
In [9]: is_palindrome("")
Out[9]: True
Thanks for reading!