This is an account of a problem solving example covered `live' in a first year lecture at the University of Kent.
"Madam I'm Adam."
A palindrome is a string of text which reads the same backwards and forwards, ifAt this stage we can start to say something about the Haskell program we are to write. We will be writing a function, whose name and type we can give at this point. We will call the function
- we disregard the punctuation (punctuation marks and spaces) in the string;
- we disregard the case (upper or lower: that is capital or small) of the letters in the string.
What is its type? It will have an argument: the string we are checking; the result of the test will be a Boolean, sopalin
palin :: String -> Bool
which says exactly that we have to reverse the string (reverse st) and compare it with the original ( ... == st). This means that we have to solve the problem of reversing a string:palin st = (reverse st == st)
How can we solve the whole problem? We need to do the same, but to a string which has had its punctuation and case disregarded:reverse :: String -> String
where the function which disregards punctuation and case ispalin st = (reverse st' == st') where st' = disregard st
which takes and returns a String.disregard :: String -> String
To reverse a string, which is a list of characters ([char]), we need to define the function from scratch (unless we look in the Haskell standard prelude!). How to start with this? We can think of this being written in stages, left hand side first:
which are the two cases of an empty string and a non-empty string whose first element is a and whose remainder (or tail) is st.reverse :: String -> String reverse [] = reverse (a:st) =
An empty string reversed is empty:
while in the general case we can be guided by an example. In this sort of definition we define reverse (a:st) using reverse st. Take the example "door". In reversing the tail we have "roo" and we get what we want by sticking "d" at the end. So,reverse [] = []
where ++ joins together two strings and [a] is the string made up of the single character a.reverse (a:st) = reverse st ++ [a]
The final problem to solve is to find disregard which as we saw above consists of two parts:
We make disregard by applying these in turn. There are two ways we might do this:remove :: String -> String change :: String -> String
Which might we choose? Here is an example of reflecting upon our design par excellence; we can choose this without having implemented either function. We choose to do the latter, since under this definition we only change those items which remain in the string. Incidentally, we can write this definition in a different way again:disregard st = remove (change st) disregard st = change (remove st)
which says that the disregard function is made by composing the functions change and remove; first remove is applied, then change is applied to the result.disregard = change . remove
What about the (a:st) case? There will be two cases, depending whether a is a punctuation character or not:remove :: String -> String remove [] = [] remove (a:st) =
In the first case, a goes into the listremove (a:st) | notPunct a = .... | otherwise = ....
In both cases the remainder of the result is got by removing punctuation from st:remove (a:st) | notPunct a = a : .... | otherwise = ....
where we can check whether we have punctuation or not in a number of ways:remove (a:st) | notPunct a = a : remove st | otherwise = remove st
where we say that ch is not one of the punctuation characters, ornotPunct :: Char -> Bool notPunct ch = not (ch=='.' || ch == ',' || .... )
where we say that we have either a letter or a character.notPunct ch = isAlpha ch || isDigit ch
Finally, we need to think how to define
Here we need to affect each character in the list by converting it;change :: String -> String
and wherechange [] = [] change (a:st) = convert a : change st
convert :: Char -> Char convert ch | isCap ch = decode (code ch + offset) | otherwise = ch where offset = code 'a' - code 'A' isCap :: Char -> Bool isCap ch = 'A' <= ch && ch <= 'Z'
An executable version of the program can be found here.