- Prerequisites
- The PhoneToWord Module
- How to Use It
- Reader Exercise
- Listing
The PhoneToWord Module
Now we can begin stepping through the module that will do all the work for us.
01: package PhoneToWord;
Line 1 defines our package name. This is the name of our module, as well.
02: use strict; 03: use Text::Ispell qw(spellcheck);
Lines 2 and 3 use() the strict pragma and Text::Ispell module, respectively. The strict pragma will turn on compile-time restrictions such as no barewords, soft references, and ensuring that we declare all our variables. Using the strict pragma is a good thing to do because it helps catch you when trying to do something which is not-so-good.
The Text::Ispell module is being used in line 3, as well as importing the spellcheck() method. This is the method that will be used to see if words that are able to be made with number combinations are actual words (or at least a word in your local dictionary file).
04: sub new { 05: my ($class,$num) = @_; 06: my $self = {}; 07: bless $self, $class;
Lines 4 through 7 begin our new() method. The new() method is what will be used to instantiate a new PhoneToWord object. Line 3 takes the two arguments that are passed when instantiation occurs. The first one will be the name of the package, or class. This will be a 'PhoneToWord' because it is the package name of this module. The second argument is the telephone number we want to work with. Line 6 defines the variable $self as an array reference. This will be the building block of our object as we go along. Finally, line 7 bless()' $self into class $class. Huh? Well, it is basically turning the $self variable into an instance of a PhoneToWord object. This is especially important if someone were to subclass this module to add their own functionality; for example, to add a way to check area codes or international numbers.
08: my @numbers = ([qw(0)], # No letters for 0 09: [qw(1)], # ditto 10: [qw(A B C)], 11: [qw(D E F)], 12: [qw(G H I)], 13: [qw(J K L)], 14: [qw(M N O)], 15: [qw(P Q R S)], 16: [qw(T U V)], 17: [qw(W X Y Z)] 18: );
Lines 8 to 18 create a list of lists with the important alphanumeric information. There are 10 entries, one for each number on the telephone, starting with 0. Each entry is an anonymous array containing the possible letters (or numbers, for 0 and 1) for the corresponding telephone digit. For example, the 4th indices (not element!) corresponds with the 4; and holds the letters G, H, and I. We will be using this to find out what the possible letters are for each digit in the telephone number.
19: $num =~ s/\D//g; 20: my @nums = split //, $num;
Line 19 removes all non-digits from the telephone number. This should give us a string of 7 digits to work with. Line 20 then creates a list of the individual numbers.
21: $self->{THREE} = [$numbers[$nums[0]], $numbers[$nums[1]], $numbers[$nums[2]]]; 22: $self->{FOUR} = [$numbers[$nums[3]], $numbers[$nums[4]], $numbers[$nums[5]], $numbers[$nums[6]]]; 23: $self->{SEVEN} = [$self->{THREE}, $self->{FOUR}];
Lines 21, 22, and 23 create properties in our object that store the values of various numbers letter representations. Line 21 creates one call THREE, which contains the first three. Lines 22 and 23 create ones named FOUR and SEVEN, respectively. For example, if the telephone number is 555-1212, the values of these properties would be like so:
THREE: [[qw(J K L)] [qw(J K L)] [qw(J K L)]] FOUR: [[qw(1)] [qw(A B C)] [qw(1)] [qw(A B C)]] SEVEN: [[qw(J K L)] [qw(J K L)] [qw(J K L)] [qw(1)] [qw(A B C)] [qw(1)] [qw(A B C)]
Now, these properties contain the possible letter combinations for each segment of the telephone number, and the entire number itself. Now that we have these things nicely separated, we will need to write the methods that will permute the combinations. More on this in a moment.
24: return $self; 25: }
Lines 24 and 25 finish off the new() method by returning the object and closing the braces. Now, on to the really fun stuff!
26: sub first_three { 27: my $self = shift; 28: my @combos = $self->permute(@{$self->{THREE}}); 29: my @retval = $self->get_words(\@combos, @{$self->{THREE}}); 30: return wantarray ? @retval : \@retval; 31: }
Ok, so I lied. We aren't really to the fun stuff just yet. We are accessing the fun stuff. Lines 26 to 31 is the first_three() method. This is the method to call when you want to get any words that are made of the first three numbers. Line 27 gets the first argument (and only argument) passed to the method, which will be a reference to the object. We save this value in the $self variable because it refers to itself. Next, line 28 calls the permute() method, and passes the values of the first three alpha combinations (hence, the name first_three()). This method, which will be covered shortly, returns an array of possible combinations. Actually, it returns all the combinations. In line 29, a reference to the list returned in line 28 is passed, along with the original list, to the get_words() method. This method, also explained a little later on, is what will actually see if the letter combinations are really words. Finally, line 30 returns the final results of possible words. wantarray() is used to account for calling scripts that desire both arrays and array references in return.
32: sub last_four { 33: my $self = shift; 34: my @combos = $self->permute(@{$self->{FOUR}}); 35: my @retval = $self->get_words(\@combos, @{$self->{FOUR}}); 36: return wantarray ? @retval : \@retval; 37: } 38: sub seven { 39: my $self = shift; 40: my @combos = $self->permute(@{$self->{SEVEN}}); 41: my @retval = $self->get_words(\@combos, @{$self->{SEVEN}}); 42: return wantarray ? @retval : \@retval; 43: }
Lines 32 to 43 make up the last_four() and seven() methods. These methods do the same thing as the first_three() method, but for the last four and all seven digits. Ok, now on to the fun stuff, honest!
44: sub permute { 45: my $self = shift; 46: my @arrays = @_; 47: my @lengths; 48: for my $array_ref (@arrays) { 49: push @lengths, scalar @$array_ref; 50: } 51: return $self->combine(@lengths); 52: }
Lines 44 to 52 comprise the permute() method. This method goes through the given list of single-letter combinations. Line 48 goes through the array of array references (described above as THREE, FOUR and SEVEN), takes the length of each reference, and pushes it onto the @lengths array. So, if the permute() method was given an array that looks like the following:
[[qw(J K L)] [qw(J K L)] [qw(J K L)]]
The @lengths array would look like this:
(3, 3, 3)
If the original array looked like (for example, the numbers 755):
[[qw(P Q R S)] [qw(J K L)] [qw(J K L)]]
Then the @lengths array would look like this:
(4, 3, 3)
So, in essence, it is figuring out how many letters are available in each slot. This is needed for the combine() method, which is called in line 51.
53: sub combine { 54: my $self = shift; 55: my $length = shift; 56: my @results; 57: for (0 .. ($length - 1)) { 58: if (@_) { 59: foreach my $result ($self->combine(@_)) { 60: push @results, $_ . $result; 61: } 62: } else { 63: push @results, $_; 64: } 65: } 66: return @results; 67: }
Lines 53 through 67 is the combine() method. It is a recursive method because it calls itself when needed. The functionality of this method is to take the results from the permute() method and combine all of the permutations. Taking the example above (4, 3, 3), the combinations would look like the following (counts are zero-based):
000 001 002 010 011 012 020 021 022 100 101 102 110 111 112 120 121 122 200 201 202 210 211 212 220 221 222 300 301 302 310 311 312 320 321 322
Can you start to see what is happening? If we take a subset of the above list of permutations and match them with the corresponding letters, it would look like this:
222 - RLL 300 - SJJ 301 - SJK 302 - SJL
Of course, none of these are words, but you should get the idea of how we are turning the numbers into letters into words. So, at the end of this, we are given an array of these numeric combinations, which our methods (first_three(), last_four(), and seven())store in the @combo array. These arrays are then passed to the get_words() method, which will map the number permutations to their letter combinations, as well as check them against the dictionary file with Text::Ispell.
68: sub get_words { 69: my $self = shift; 70: my $combos = shift; 71: my @uses = @_; 72: my @ret; 73: my $words = join " ", $self->show_text($combos, @uses); 74: eval { 75: for my $word (spellcheck($words)) { 76: if ($word->{type} =~ /(?:ok|compound)/) { 77: push @ret, $word->{term}; 78: } 79: } 80: }; 81: return @ret; 82: }
Lines 68 to 82 define the get_words() method, which is called by the three accessor methods: first_three(), last_four(), and seven(). It takes two arguments: an array reference with all the combinations gathered from the permute() and combine() methods, as well as the array of possible letters (which we stored in the appropriate object property THREE, FOUR, or SEVEN).
On line 73, a new scalar, $words, is created by joining the results of the show_text() method by spaces. We will cover the show_text() method in moment, but in order for the rest of this method to make sense, just know that it returns an array of possible words. So, if the three numbers 344 were being inspected, the $words scalar would look something like this:
"DGG DGH DGI DHG DHH DHI DIG DIH DII EGG EGH EGI EHG EHH EHI EIG EIH EII FGG FGH FGI FHG FHH FHI FIG FIH FII"
That is the possible combinations of letters corresponding to the numbers 344. Next, lines 74 to 80 is an eval() that will use Text::Ispell::spellcheck to see if these are actual words. Line 75 starts looping through the list returned by spellcheck(). The spellcheck() method will return a hash reference for each of the possible words passed to it. As we iterate through this list, line 76 inspects the type keyword in the hash reference. If the type keyword contains the words 'ok' or 'compound' (meaning it is a compound word), we have a match! We then push() all the matches onto the @ret array. The actual word that matched is stored in the term keyword of the $word hash reference. Once we iterate through all the words, line 81 returns the final list of word matches.
83: sub show_text { 84: my ($self, $combos, @arrays) = @_; 85: my @all; 86: foreach my $combo (@$combos) { 87: my $i = 0; 88: my $text; 89: for my $elem (split'', $combo) { 90: $text .= $arrays[$i++]->[$elem]; 91: } 92: push @all, $text; 93: } 94: return @all; 95: }
Lines 83 through 95 make up show_text(), which is called in the get_words() method. When this method is called, it is given two arguments: an array reference ($combos) and the array of array references that holds all the possible letters for the original numbers we are trying to match (that is, what is in the THREE, FOUR, or SEVEN object properties).
Lines 86 to 93 iterates through all the possible combinations in $combo. As this happens, another iteration occurs on the $combos array reference. Each element in @$combos is a permutation from the perlmute() method. In line 87, the $i variable is initialized. This will be used as a counter in the coming lines. Line 88 initializes the $text variable, which will be used to create the letter combinations.
Line 89 begins another iteration. This time, we iterate over each number for each number permutation. So, if $combo is 220, these are split apart and assigned to $elem.
Line 90 creates the letter combination. Again, take for example that $combo is 220first, the 2 will be looked at; then another 2; then 0. As these three iterations happen, $text is appended to, based on the appropriate value of $i and $elem. Here is what it would look like with 220:
I is [[qw(D E F)] [qw(G H I)] [qw(G H I)]] I is 0 I is 2 I is appended with I[2]> which is F I is 1 I is 2 I is appended with I[2]> which is I I is 2 I is 0 I is appended with I[0]> which is G
In line 92, the word 'FIG' is then pushed onto the @all array, and the next permutation is converted to letters. When this is complete, the final @all array is returned on line 94.
Now, line 75 may make more sense because you can see where and how each word that is passed to spellcheck() gets created.
96: 1;
Line 96 makes sure that our module returns a true value when it is being use()d.