# string_recursion.py
# Walker M. White (wmw2)
# September 9, 2012
"""A module of recursive functions on Strings."""
import string # constants to help with string

def length(s):
    """Returns: number of characters in s
    
    Precondition s is a string"""
    assert type(s) == str, `s` + ' is not a string' # get in the habit

    # {s is empty}
    if s == '':
        return 0

    # { s has at least one character}
    # return (number of 'e's in s[0]) + (number of 'e's in s[1:])
    return (1 if s[0] == 'e' else 0) + num_e(s[1:])

def num_e(s):
    """Returns: number of 'e's in s
    
    Precondition s is a string"""
    assert type(s) == str, `s` + ' is not a string' # get in the habit

    # {s is empty}
    if s == '':
        return 0

    # { s has at least one character}
    # return (number of 'e's in s[0]) + (number of 'e's in s[1:])
    return (1 if s[0] == 'e' else 0) + num_e(s[1:])


def deblank(s):
    """Returns: s but with blanks removed
    
    Precondition s is a string"""
    assert type(s) == str, `s` + ' is not a string' # get in the habit

    # {s is empty}
    if s == '':
        return s

    if s[0] in string.whitespace:
        return deblank(s[1:])
    
    return s[0] + deblank(s[1:]);


def depunct(s):
    """Returns: s but with everything that is not a letter removed 

    Precondition s is a string"""
    assert type(s) == str, `s` + ' is not a string' # get in the habit
    
    # {s is empty}
    if s == '':
        return s

    if not s[0] in string.letters:
        return depunct(s[1:])
    
    return s[0] + depunct(s[1:]);

def reverse(s):
    """Returns: s with its characters in reverse order

    Precondition s is a string"""
    assert type(s) == str, `s` + ' is not a string' # get in the habit

    if len(s) < 2:
        return s
        
    # { 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 string"""
    assert type(c) == str, `c` + ' is not a string' # get in the habit
    assert type(d) == str, `c` + ' 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, `s` + ' is not a string' # get in the habit

    # print 's is: ' + `s`
    if len(s) < 2:
        return True
    
    # { s has at least 2 characters }
    # return (first and last chars match) && (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"""
    assert type(s) == str, `s` + ' is not a string' # get in the habit
    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"""
    assert type(s) == str, `s` + ' is not a string' # get in the habit
    return ispalindrome(depunct(s))