This is where the magic happens:
return (s[0] == s[-1]) and isPal(s[1:-1])
(I have added parentheses, so you are absolutely clear on the operator precedence). It is important to know that the Python AND statement will evaluate the first argument, and only if it is True
, will it evaluate the second argument. If the first was False
, then it will return False
immediately without calling isPal()
. This is known as short-circuit evaluation. Blender makes a good example of this in his post.
s[0]
is the first letter in the string, s[-1]
is the last letter in the sting. First we check to see if the first and last letters are the same. If they are different, then there is no way the string can be a palindrome. If the are the same we can disregard them and move onto the inner part of the string.
s[1:-1]
trims off the first and last letters, and then passes that to the isPal()
function, which happens to be the function we are currently in -- this is called recursion. When we 'recurse' into the function again, a new stack is created, new local variables are created, and the input arguments to the function will be different to the ones in the previous call.
We continue to check the first and last letters, and recurse all the way to the centre of the word. At this point there are either 1 or 0 characters left, and if we have got that far we know we have found a palindrome.
When we return from this final, inner, call to isPal()
the return value (True
if a palindrome has been found) is passed to the calling function, which then returns it to its calling function, etc., until the final 'outside' isPal()
function returns the result to the initial caller. We call this process 'unrolling the stack'.
Here is an example:
s = 'racecar'
s[0] == s[-1] # True - both letters are 'r'
s = s[1:-1] # s becomes 'aceca'
s[0] == s[-1] # True - both letters are 'a'
s = s[1:-1] # s becomes 'cec'
s[0] == s[-1] # True - both letters are 'c'
s = s[1:-1] # s becomes 'e'
len(s) <= 1 # This statement is now True, so the stack will start to unwind
Visually, the calls will look like this:
'racecar' ---------
|
V 'aceca'
True <--- isPal( ) ----
^ |
True | V 'cec'
---- isPal( ) ----
^ |
True | V 'e'
---- isPal( ) ----
^ |
True | V
---- isPal( ) # We get all the way down here,
# and then we start to return
In the case where we do not have a palindrome, this will happen:
'race2car' --------
|
V 'ace2ca'
False <--- isPal( ) ----
^ |
False | V 'ce2c'
---- isPal( ) ----
^ |
False | V 'e2'
---- isPal( ) ----
^ |
False | V
---- isPal( ) # Here the outer two letters
# do not match, so we start to
# return False