# string_recursion.py # Walker M. White (wmw2), Lillian Lee (LJL2) # Mar 5, 2014 """A module of recursive functions on strings.""" # The functions in this module use assert statements, in this case, to # check that the types of the argument values are expected. Assert statements # can be used in any kind of code, not just recursive functions. # They halt execution of Python, printing out the message you supply, if the # boolean expression is false. # # In this code, assert statements are being used to verify that the types of # the argument values are correct, as a way of explicitly enforcing preconditions. # Their error messages involve calls to the function "repr", which returns a # string that represents *unambiguously* the object given as argument. As such, # its output can differ from that of str(). The book is a little light on this # topic; a dense but perhaps useful post is this: # http://stackoverflow.com/questions/1436703/difference-between-str-and-repr-in-python import string # constants to help with string processing def length(s): """Returns: number of characters in s Precondition s is a string""" assert type(s) == str, repr(s) + ' is not a string' # get in the habit # {s is empty} - base case if s == '': return 0 # {s has at least one character} - recursive case # return 1 + length of the rest of s return 1 + length(s[1:]) def length2(s): """Returns: number of characters in s Precondition s is a string""" assert type(s) == str, repr(s) + ' is not a string' # get in the habit # This implementation uses if-else, rather than using the fact that "return" # instantly exits the function call. if s == '': return 0 else: # {s has at least one character} - recursive case # return 1 + length of the rest of s return 1 + length2(s[1:]) def num_e(s): """Returns: number of 'e's in s Precondition s is a string""" assert type(s) == str, repr(s) + ' is not a string' # get in the habit # {s is empty} - base case if s == '': return 0 else: # { s has at least one character} - recursive case # return (number of 'e's in s[0]) + number of 'e's in the rest of s # # this implementation uses a trick: if len(s) ==1, s[1:] == '' return (1 if s[0] == 'e' else 0) + num_e(s[1:]) def num_e_2(s): """Returns: number of 'e's in s Precondition s is a string""" # More straightforward implementation in terms of cases; # but more verbose if s =='': return 0 elif len(s) == 1: if s == 'e': return 1 else: return 0 else: return (1 if s[0] == 'e' else 0) + num_e_2(s[1:]) def num_e_3(s): """Returns: number of 'e's in s Precondition s is a string""" # Non-"else-ist" version if s =='': return 0 # this implementation uses a trick: if len(s)==1, s[1:] == '' return (1 if s[0] == 'e' else 0) + num_e_3(s[1:]) def deblank(s): """Returns: copy of s but with blanks removed Precondition s is a string""" assert type(s) == str, repr(s) + ' is not a string' # get in the habit # {s is empty} - base case if s == '': return s else: # {s has at least one character} - recursive cases if s[0] in string.whitespace: # {There's a blank to be deleted in the first character} return deblank(s[1:]) else: return s[0] + deblank(s[1:]) def depunct(s): """Returns: copy of s with everything that is not a letter removed Precondition s is a string""" assert type(s) == str, repr(s) + ' is not a string' # get in the habit # {s is empty} if s == '': return s else: # {s has at least one character} - recursive cases if not s[0] in string.letters: return depunct(s[1:]) else: return s[0] + depunct(s[1:]) def reverse(s): """Returns: copy of s with its characters in reverse order Precondition s is a string""" assert type(s) == str, repr(s) + ' is not a string' # get in the habit if s == '': # base case return s else: # { s has at least 2 chars } return reverse(s[1:]) + s[0] def equals_ignore_case(c, d): """Returns: true if strings c and d differ only in case, if at all Precondition c, d are strings""" # Not implemented recursively assert type(c) == str, repr(c) + ' is not a string' # get in the habit assert type(d) == str, repr(d) + ' is not a string' # get in the habit return c.upper() == d.upper() def ispalindrome(s): """Returns: true if s is a palindrome There are two ways to define a palindrome: 1. s is a palindrome if it reads the same backward and forward. 2. s is a palindrome if either (1) its length is <= 1 OR (2) its first and last chars are the same and the string between them is a palindrome. Letters that differ only in case are considered to match. Precondition s is a string""" assert type(s) == str, repr(s) + ' is not a string' # get in the habit # This implementation explicitly checks front end against the back end # of the string. # This kind of print statement can help a lot in debugging, # but we've commented it out here. # print 's is: ' + repr(s) if len(s) < 2: return True else: # { s has at least 2 characters } # return (first and last chars match) and (inner substring is pal.) return (equals_ignore_case(s[0], s[-1]) and ispalindrome(s[1:len(s)-1])) def ispalindrome2(s): """Returns: true if s is a palindrome There are two ways to define a palindrome: 1. s is a palindrome if it reads the same backward and forward. 2. s is a palindrome if either (1) its length is <= 1 OR (2) its first and last chars are the same and the string between them is a palindrome. Letters that differ only in case are considered to match. Precondition s is a string""" # Non-explicitly-recursive implementation of same spec as ispalindrome return equals_ignore_case(s,reverse(s)) def ispalindrome_loosely(s): """Returns: true if s is a palindrome paying attention only to the letters Case and any non-letter characters are ignored. Precondition s is a string""" # Not a recursive function assert type(s) == str, repr(s) + ' is not a string' # get in the habit return ispalindrome(depunct(s))