Our palindrome program works well, however we'd like to extend it to ignore spaces and non-alphabetical characters in the input.
For example, we'd like it to identify the following as a palindrome:
"A man, a plan, a canal: Panama."
However, right now, the simplistic algorithm we use says this is not a palindrome:
"A man, a plan, a canal: Panama." palindrome? .
f
We would like it to output
t there. We can encode this requirement with a unit test that we add to
palindrome-tests.factor:
{ t } [ "A man, a plan, a canal: Panama." palindrome? ] unit-test
If you now run unit tests, you will see a unit test failure:
"palindrome" test
The next step is to, of course, fix our code so that the unit test can pass.
We begin by writing a word which removes blanks and non-alphabetical characters from a string, and then converts the string to lower case. We call this word
normalize. To figure out how to write this word, we begin with some interactive experimentation in the listener.
Start by pushing a character on the stack; notice that characters are really just integers:
CHAR: a
Now, use the
Letter? word to test if it is an alphabetical character, upper or lower case:
Letter? .
t
Note: you might receive an error message that asks if you want to use the
ASCII or
Unicode support versions of the
Letter? word. Choosing the Unicode version will allow Factor to continue running your code.
This gives the expected result.
Now try with a non-alphabetical character:
CHAR: #
Letter? .
f
What we want to do is given a string, remove all characters which do not match the
Letter? predicate. Let's push a string on the stack:
"A man, a plan, a canal: Panama."
Now, place a quotation containing
Letter? on the stack; quoting code places it on the stack instead of executing it immediately:
[ Letter? ]
Note:
Quotations are similar to anonymous functions or blocks of code that have not been executed yet.
Finally, we pass the string and the quotation to the
filter word, which will run your quotation and return a new string that contains only characters for which
Letter? returns "true":
filter
The stack should now contain the following string:
AmanaplanacanalPanama. This is almost what we want; we just need to convert the string to lower case now. This can be done by calling
>lower; the
> prefix is a naming convention for conversion operations, and should be read as "to":
>lower
Finally, let's print the top of the stack and discard it:
.
This will output
amanaplanacanalpanama. This string is in the form that we want, and we evaluated the following code to get it into this form:
[ Letter? ] filter >lower
This code starts with a string on the stack, removes non-alphabetical characters, and converts the result to lower case, leaving a new string on the stack. We put this code in a new word, and add the new word to
palindrome.factor:
: normalize ( string -- string' ) [ Letter? ] filter >lower ;
You will need to add
unicode to the vocabulary search path, so that
>lower and
Letter? can be used in the source file.
We modify
palindrome? to first apply
normalize to its input:
: palindrome? ( string -- ? ) normalize dup reverse = ;
Factor compiles the file from the top down. So, be sure to place the definition for
normalize above the definition for
palindrome?.
Now if you press
F2, the source file should reload without any errors. You can run unit tests again, and this time, they will all pass:
"palindrome" test
Congratulations, you have now completed
Your first program!