I'm working my way through the MIT 6.00 class on OpenCourseWare and I'm having a little trouble with the lecture on recursion. I think I understand the basic idea, that you can break some problems down into smaller, repeatable problems. Where I'm having trouble is understanding how this works in the actual code. There is one specific example that I don't quite understand...
def toChars(s):
import string
s = string.lower(s)
ans = ''
for c in s:
if c in string.lowercase:
ans = ans + c
return ans
def isPal(s):
if len(s) <= 1:
return True
else:
return s[0] == s[-1] and isPal(s[1:-1])
def isPalindrome(s):
"""Returns True if s is a palindrome and False otherwise"""
return isPal(toChars(s))
This code is supposed to check if a string is a palindrome or not. I am a little lost on how isPal(s) works. Here's the steps as I'm reading them...
Check if s is one character or less, if it is return True.
If s has more than one character return a Boolean value (True or False) from the expression comparing the first and last character. So basically check if the first and last letter are the same.
AND return the result of the function run again with the first and last letters stripped from the string.
What is confusing me is the return s[0] == s[-1] and isPal(s[1:-1]) bit. I'm not sure I understand what it means to both return True/False based on the first expression AND return the function. Honestly I'm not even sure what returning the function actually means, I'm guessing it just runs the function again, but doesn't actually return any value. I also don't understand how this can actually tell if a string is a palindrome or not. Wouldn't it just keep hitting the second part of return s[0] == s[-1] and isPal(s[1:-1]) until the string, whether it was a palindrome or not, was reduced to 1 or 0 characters? The only thing I can think of is that returning a false value from return s[0] == s[-1] exits the function with a return of false? But I don't feel like that's actually how Python works, at least in my study so far I haven't run into anything saying that returning a False in a function would prevent the second part of the return statement from executing, which would call the function again regardless of whether the first and last letters where the same.
I'm kind of banging my head against the wall at this point, so any insight would be really appreciated!
Perhaps the best way is to go step by step with the values for that line that is confusing you.
return s[0] == s[-1] and isPal(s[1:-1])
Assuming the String you're testing for is "ABCDBA", here is the sequence of calls
return s[0] == s[-1] and isPal(s[1:-1]) #evaluate to ("A" == "A" and isPal("BCDB")) - need to go deeper
return s[0] == s[-1] and isPal(s[1:-1]) #evaluate ("B" == "B" and isPal("CD")) - need to go deeper
return s[0] == s[-1] and isPal(s[1:-1]) #returns ("C" == "D")
Note that in the last line we didn't evaluate for isPal because of short-circuiting - we know that False and X where X can be True or False is always False. Read more about that here.
At the last line we're 4 functions deep into isPal. Since the last line doesn't require us evaluate for isPal() again, we start "rising to the surface" back to the original invocation of isPal which was at
def isPalindrome(s):
"""Returns True if s is a palindrome and False otherwise"""
return isPal(toChars(s))
One clarification which can help you understand what's going on: return ...X and ...Y means: Compute the value of X. If the value is false, return it, otherwise compute the value of Y, and return it.
More info about the short-circuit, lazy behavior (i.e. not evaluating Y if not needed) of the and and or operators in Python: https://docs.python.org/release/2.7/reference/expressions.html#boolean-operations
So that statement doesn't make the function return two values (X and Y), but one value which is the logical AND of X and Y: it's true iff both X and Y are (evaluated to) true.
Also it's worth distinguishing function from function call. There can be many active calls to the same function, with different arguments, local variables etc., so their return values can also be different (as soon as they return). When a function calls itself, that's called recursion. When a function call returns, execution continues in the caller, which can be the same function (but of course a different, upper-level function call) in case of recursion.
If you run the isPal function on "heeeeh" with print statements:
def isPal(s):
if len(s) <= 1:
return True
else:
print s[0],s[-1]
return s[0] == s[-1] and isPal(s[1:-1])
print isPal("heeeeh")
You get:
h h
e e
e e
True
as each char at index s[0] and s[-1] are equal through each recursive call as the string is a palindrome.
Running it on "aeeee":
def isPal(s):
if len(s) <= 1:
return True
else:
print s[0],s[-1]
return s[0] == s[-1] and isPal(s[1:-1])
print isPal("aeeeeh")
You get :
a h
False
As as soon as s[0] and s-[1] are not equal the functions ends and returns False as it cannot be a palindrome if the chars are not equal.
I'll add my explanation, maybe this will help you.
First things first:
def toChars(s):
import string
s = string.lower(s)
ans = ''
for c in s:
if c in string.lowercase:
ans = ans + c
return ans
will simply take some text and turn it all to lower case, character by character.
Next is:
def isPal(s):
if len(s) <= 1:
return True
else:
return s[0] == s[-1] and isPal(s[1:-1])
To understand this part you ask yourself a question, what is a palindrome and what are ways to know if word or sentence is one.
In this example approach is: palindrome is word or sentence where its characters in same distance from beginning and from the end are the same characters.
So how to check that? Well, first if your word is just one letter or shorter then it's a palindrome.
Then you check if letters at the beginning and the end of your word are the same.
You remember the result as true or false and strip your word from letters you already checked and repeat the process until your word is 1 or less characters long and check if every result of comparison was true.
Example:
Foob->foob
f=b? NO, false
Example 2:
Fbof->fbof
f=f? Yes, true,
bo
b=o? No, false.
true and false = false, so the word is not a palindrome
Example 3:
Fobof->fobof
f=f? Yes, true,
obo,
o=o? Yes, true
b
len(b) = <=1 ? Yes, true,
true and true and true = true, so the word is a palindrome.
Hope it helps, ask if you need more thorough explanation, I'll try to help, no worries, i struggled with recursion at the beginning as well.
Related
I am trying to learn while loops.
To practice, I created a simple while loop with an If statement inside a function to check and see if a word is a palindrome. For some reason, even if the word is not a palindrome, it keeps returning True.
I expected the output of the print function on the last line to be False:
from collections import deque
word = "tacrocat"
def check_palindrome(word):
d = deque(word)
while len(d) > 1:
if d.pop() == d.popleft():
return True
return False
print(check_palindrome(word))
NOTE: When I change the if statement evaluation to "!=", change the return statement inside the if statement to False, and change the return statement in the while loop to True, it seems to accurately detect palindromes - but I have no idea why.
If the program is written like this:
from collections import deque
word = "tacrocat"
def check_palindrome(word):
d = deque(word)
while len(d) > 1:
if d.pop() == d.popleft():
return True
return False
print(check_palindrome(word))
At iteration-1:
Since both the first character('t') and last character('t') in the deque are equal it will enter the if condition and return True. When a return statement is executed in a function, then the control comes out of the function. In other words, as soon as return True statement is executed, the control comes back to print statement without executing remaining iterations of while loop and since we returned True, so True will be printed as output.
Let's analyze second program:
from collections import deque
word = "tacrocat"
def check_palindrome(word):
d = deque(word)
while len(d) > 1:
if d.pop() != d.popleft():
return False
return True
print(check_palindrome(word))
At iteration-1:
Current deque: [t,a,c,r,o,c,a,t]
We are popping both first element and last element and checking whether they are not equal in if condition. Since 't' and 't' are equal, if condition will not execute and while loop will continue.
At iteration-2:
Current deque: [a,c,r,o,c,a]
Since 'a' and 'a' are equal, if condition will not execute and while loop will continue. Both first and last elements are popped (pop and popleft)
At iteration-3:
Current deque: [c,r,o,c]
Since 'c' and 'c' are equal, if condition will not execute and while loop will continue. Both first and last elements are popped (pop and popleft)
At iteration-4:
Current deque: [r,o]
Since 'r' and 'o' are not equal, if condition will execute and return False statement is executed. So, the function check_palindrome will terminate with return value as False and so we get output as False.
The second program is correct because to check if a word is palindrome or not, we need to check all letters whether they are satisfying the condition or not. Just checking only the first and last character of a word and if they are equal, then it doesn't mean the remaining letters are same. We need to check them too.
Once it finds a return True it will come out of the function.
In the first iteration it will compare t with t and exit the function.
In your case it comes out at first letter.
rather you can do as stated by #Goodies in comment
from collections import deque
word = "tacrocat"
def check_palindrome(word):
d = deque(word)
while len(d) > 1:
if d.pop() != d.popleft():
return False
return True
print(check_palindrome(word))
This is happening because it is returning True if any one of the characters on either side of centre are equal. This is probably confusing but basically it checks if any of the letters are mirrored not all of them because the return True exits early.
This means that, actually, your function does not always return True, for example a word like "word" would return false.
To fix this, rather than return True when one match is found and otherwise return False, you could return False if any do not match and else return True like so (see comment above by #Goodies):
from collections import deque
word = "tacrocat"
def check_palindrome(word):
d = deque(word)
while len(d) > 1:
if d.pop() != d.popleft():
return False
return True
print(check_palindrome(word))
which (correctly) outputs:
False
I'm wondering how to break out of a recursive loop to the main function. I am trying to do a simple palindrome exercise. The function should return True for "redivider" but the return True is being passed to is_pal() and the function isn't breaking. Short of adding a second variable to is_pal to track True/False, what is the proper way to break out of this recursive loop?
def first(word):
return word[0]
def last(word):
return word[-1]
def middle(word):
return word[1:-1]
def is_pal(str):
if len(str) == 1:
return True
if first(str) == last(str) and len(str) > 1:
is_pal(middle(str))
print is_pal("redivider")
One way to break out of a recursive function in Python is to throw an exception and catch that at the top level. Some people will say that this is not the right way to think about recursion, but it gets the job done. Furthermore, if the task is to identify "problem" elements in an array/array of arrays/ndarray etc., a break technique is convenient, because it stops the algorithm from continuing after the global solution has been identified.
def solve_problem(lst):
def solve_rec(l):
'''has impl. that may throw an exception '''
try:
solve_rec(lst)
return True
except:
return False
def is_pal(str):
if len(str) <= 1:
return True
if first(str) == last(str):
return is_pal(middle(str))
else:
return False
That way, if they don't match, False is returned; if it makes it all the way to the end, True is returned. I also eliminated a redundant conditional and checked for the edge-case of an even-length palindrome.
You don't "break" out of recursive functions. Trying to do so says you're thinking about them the wrong way. Currently your recursive call is ignoring the output, which means that the recursion is pointless; whatever is_pal(middle(str)) returns has no effect on the return value of your function.
A recursive algorithm solves the input problem by decomposing the problem into a smaller problem, getting the solution to the smaller problem recursively, and then using the smaller solution to construct a correct solution to the larger problem. You don't "break" out of the inner calls, you return a solution back up one level. You don't know (or need to know) whether you're in an inner call or a top level call. In either case, your function should do the same thing: return True if the argument is a palindrome, and False if it isn't.
The algorithm you're trying to implement is basically this:
If the string is of length 1, it's a palindrome (return True)
Otherwise, if the first character is the same as the last character, then the input is a palindrome if the middle characters are a palindrome.
So what this means is that once you've established the first and last characters are the same, the answer to "is my input a palindrome" is exactly the same as the answer to "are the middle characters a palindrome". You need to return that answer to fulfil your contract. So the recursive call should be return is_pal(middle(str)) rather than just is_pal(middle(str)). If this was a top level call, then that's the answer; if this wasn't a top-level call, then the outer call is going to need this answer to work out the answer to the outer problem (in this case, by simply returning it).
Btw, your algorithm also has some other problems.
You never return False, so the answer can never be False (in this case you happen to accidentally return None by falling off the end of the function if the first and last character don't match, and None will probably do as a stand in for False in most cases, but it's still not really correct).
If the string's length is zero rather than 1 (which will happen if an empty string is passed in or if a palindrome of even length is passed in once all the pairs of equal first and last characters are stripped off), then you don't return the correct answer, and in fact you try to get the first and last character of the empty string, which will cause an exception.
You can exit the program after printing the results using the exit() function.
That may not be a good practice, but it might be what you're looking for.
You're missing a return. Also, don't use str as a variable name. Last thing, the first and last functions could be named slightly better.
def first_letter(word):
return word[0]
def last_letter(word):
return word[-1]
def middle(word):
return word[1:-1]
def is_pal(word):
if len(word) == 1:
return True
if first_letter(word) == last_letter(word) and len(word) > 1:
return is_pal(middle(word))
print is_pal("redivider")
You need to return False in case they don't match and add a return statement. Also you will probably want to check against len(str)==0 and len(str)==1:
def is_pal(str):
if len(str) in [0, 1]:
return True
if first(str) == last(str) and len(str) > 1:
return is_pal(middle(str))
else :
return False
YOU CAN BREAK RECURSION BY RETURNING 1 in 'if' statement before you write your 'function()' 2nd time.
I mean that's what we do to find factorial !! RIGHT? :)
The problem is formulated as follows:
Write a recursive function that, given a string, checks if the string
is formed by two halves equal to each other (i.e. s = s1 + s2, with s1
= s2), imposing the constraint that the equality operator == can only be applied to strings of length ≤1. If the length of the string is
odd, return an error.
I wrote this code in Python 2.7 that is correct (it gives me the right answer every time) but does not enter that recursive loop at all. So can I omit that call here?
def recursiveHalfString(s):
##param s: string
##return bool
if (len(s))%2==0: #verify if the rest of the division by 2 = 0 (even number)
if len(s)<=1: # case in which I can use the == operator
if s[0]==s[1]:
return True
else:
return False
if len(s)>1:
if s[0:len(s)/2] != s[len(s)/2:len(s)]: # here I used != instead of ==
if s!=0:
return False
else:
return recursiveHalfString(s[0:(len(s)/2)-1]+s[(len(s)/2)+1:len(s)]) # broken call
return True
else:
return "Error: odd string"
The expected results are True if the string is like "abbaabba"
or False when it's like anything else not similat to the pattern ("wordword")
This is a much simplified recursive version that actually uses the single char comparison to reduce the problem size:
def rhs(s):
half, rest = divmod(len(s), 2)
if rest: # odd length
raise ValueError # return 'error'
if half == 0: # simplest base case: empty string
return True
return s[0] == s[half] and rhs(s[1:half] + s[half+1:])
It has to be said though that, algorithmically, this problem does not lend itself well to a recursive approach, given the constraints.
Here is another recursive solution. A good rule of thumb when taking a recursive approach is to first think about your base case.
def recursiveHalfString(s):
# base case, if string is empty
if s == '':
return True
if (len(s))%2==0:
if s[0] != s[(len(s)/2)]:
return False
else:
left = s[1:len(s)/2] # the left half of the string without first char
right = s[(len(s)/2)+1: len(s)] # the right half without first char
return recursiveHalfString(left + right)
else:
return "Error: odd string"
print(recursiveHalfString('abbaabba')) # True
print(recursiveHalfString('fail')) # False
print(recursiveHalfString('oddstring')) # Error: odd string
This function will split the string into two halves, compare the first characters and recursively call itself with the two halves concatenated together without the leading characters.
However like stated in another answer, recursion is not necessarily an efficient solution in this case. This approach creates a lot of new strings and is in no way an optimal way to do this. It is for demonstration purposes only.
Another recursive solution that doesn't involve creating a bunch of new strings might look like:
def recursiveHalfString(s, offset=0):
half, odd = divmod(len(s), 2)
assert(not odd)
if not s or offset > half:
return True
if s[offset] != s[half + offset]:
return False
return recursiveHalfString(s, offset + 1)
However, as #schwobaseggl suggested, a recursive approach here is a bit clunkier than a simple iterative approach:
def recursiveHalfString(s, offset=0):
half, odd = divmod(len(s), 2)
assert(not odd)
for offset in range(half):
if s[offset] != s[half + offset]:
return False
return True
I have the following code, when going through the python, the options aaabaaaa, zzzazzazz gave me the false test.Here is the code, I am not too sure on how to fix it.
def checkPalindrome(inputString):
n=len(inputString)
#if string is one letter
if n==1:
return True
#if string has more than one letter
for i in range (0, math.floor(n/2)) :
if inputString[i]!=inputString[n-1-i]:
return False
else:
return True
You have a few issues. The main issue here is that your else clause has a return True inside the loop. What you'd want to do is finish iterating over the string before returning True. If you are familiar with boolean logic, this is the equivalent of short circuiting with AND.
The other issue (not really an issue, more a nitpick) is that you can just use integer division //, instead of having to import math's floor function.
So,
def isPalindrome(string):
for i in range(0, len(string) // 2):
if string[i] != string[-(i + 1)]:
return False
return True
Another way of handling this would be using all:
def isPalindrome(string):
return all(x == y for x, y in zip(string, reversed(string)))
Or, taking advantage of python's convenient slice notation for the most concise solution possible, we have:
def isPalindrome(string):
return string == string[::-1]
Try this which uses array slicing (reversing an array of chars)
def checkPalindrome(inputString):
n=len(inputString)
#if string is one letter
if n==1:
return True
#if string has more than one letter
return inputString==inputString[::-1]
Another approach could be using slicing. Strings can be accessed by index like arrays/lists and also be inverted like this.
def isPalindrom(string)
return string == string[::-1]
the [::-1] slicing returns the reversed string, the comparision with the original string is True if it's the same otherwise false.
so I'm new to programming (and python) and I have to make this program that returns True if the string has zero or one dot characters ("." characters) and return False if the string contains two or more dots
here is what I currently have, I cannot get it to work for me, please correct me if I am wrong, thanks!
def check_dots(text):
text = []
for char in text:
if '.' < 2 in text:
return True
else:
return False
Use the builtin Python function list.count()
if text.count('.') < 2:
return True
It can be even shorter if instead of an if-else statement, you do
return text.count('.') < 2
Also, there are some errors in your function. All you need to do is
def check_dots(text):
return text.count('.') < 2
A correct and shorter version would be:
return text.count('.') <= 1
Python has a function called count()
You can do the following.
if text.count('.') < 2: #it checks for the number of '.' occuring in your string
return True
else:
return False
A shortcut would be:
return text.count('.')<2
Let's analyze the above statement.
in this part, text.count('.')<2: It basically says "I will check for periods that occur less than twice in the string and return True or False depending on the number of occurences." So if text.count('.') was 3, then that would be 3<2 which would become False.
another example. Say you want it to return False if a string is longer than 7 characters.
x = input("Enter a string.")
return len(x)>7
The code snippet len(x)>7 means that the program checks for the length of x. Let's pretend the string length is 9. In this case, len(x) would evaluate to 9, then it would evaluate to 9>7, which is True.
I shall now analyze your code.
def check_dots(text):
text = [] ################ Don't do this. This makes it a list,
# and the thing that you are trying to
# do involves strings, not lists. Remove it.
for char in text: #not needed, delete
if '.' < 2 in text: #I see your thinking, but you can use the count()
#to do this. so -> if text.count('.')<2: <- That
# will do the same thing as you attempted.
return True
else:
return False