My son came home from 5th grade with an interesting assignment for finding something called dollar words. As I understand it, you assign the values 1-26 to your letters and then look for words whose letter values sum up to 100. (These are exactly worth a dollar if you think of the letter values as being in cents.)
Naturally, this is the sort of problem that is really easy to solve in Raku. In fact, I think carefully explaining what the program is going to do is going to take me considerably longer than writing it in the first place did! First, here’s the full program:
sub value($word) {
[+] $word.lc.comb.map({ $_.ord - "a".ord + 1 });
}
my @words = "/usr/share/dict/words".IO.lines;
for @words.grep({ value($_) == 100 }) -> $dollar-word {
say $dollar-word;
}
Breaking it down, piece by piece:
sub value($word) {
[+] $word.lc.comb.map({ $_.ord - "a".ord + 1 });
}
This creates a subroutine called value
which takes a word (in the variable $word
) and determines its value under this system. Step-by-step:
-
[+]
takes the sum of a list that follows it. $word.lc
takes the variable $word and transforms it to lower-case letters. (The value chart doesn’t care whether a letter is upper or lower case, so we simplify the problem.).comb
takes the result of that and translates it to a list of letters. (It’s named that because the full version of the command combs through text looking for things that match a pattern — but if you do not specify the pattern to match, it just returns one letter at a time.).map
takes a list of items and applies an operation to each of them, returning another list of items. In this case we have a list of letters and want them to be a list of numbers.{ $_.ord - "a".ord + 1 }
is the operation.$_
here is the input value, and.ord
takes a letter and returns its Unicode code point, the unique number the computer assigns to each letter when it stores it internally. Because the letters a-z are stored in increasing order from 97-122, this is almost what we want. We subtract the code point for “a”, which means now we have a-z mapped to 0-25. Then we add one to get the value we want.
my @words = "/usr/share/dict/words".IO.lines;
This is how we get a list of words to check for dollar words. On a Mac or Linux computer, "/usr/share/dict/words"
is a file with a complete list of default words for spell-checking. This code uses the lines
routine to read a file and return a list where each line in the file becomes an item in the list — in this case, each word is on its own line, so it’s a list with lots of words. (On my Mac, it’s 235,886 words.) It stores that list in the variable @words
.
for @words.grep({ value($_) == 100 }) -> $dollar-word {
say $dollar-word;
}
And now we reach the home stretch. grep
is sort of like map
, but instead of applying an operation to each item in a list and returning a list of the results, grep
applies an operation to each item in the list and and only returns the items from the original list whose operation returns the value True
. In this case, the operation is { value($_) == 100 }
, which passes the input $_
to the value
subroutine we defined above and then checks to see if the result it returns equals 100. So once grep
has done its job, we have a list of dollar words.
We use for
to go over that list of dollar words, take each word, and (one-by-one) assign it to the variable $dollar-word
, then perform the operation say $dollar-word
, which prints the resulting word. So taken together this prints out the full list of words from the "/usr/share/dict/words"
file which are dollar words. On my computer, this finds 2,303 dollar words.