An Example: More Names

On Day 5, we had a simple example that read in names (first and last), and then split those names into a hash of names keyed by the last name. On Day 6, both in the body of the lesson and in the exercises, we used the <> operator to read those names into a hash from an external file. Let's extend the names script here so that it doesn't just read the names into a hash, it also does something with those names. This version of the names script, morenames.pl, adds a large while loop that gives you a list of four options:

  • Sort and print the names list by last name

  • Sort and print the names list by first name

  • Search for first or last name

  • Quit

If you choose any of the options to search or sort the list, the program repeats and allows you to choose another option. The fourth option exits the program. Here's a transcript of how the program might look when run:

$ morenames.pl names.txt

1. Sort names by last name
2. Sort names by first name
3. Search for a name
4. Quit

Choose a number: 1
Adams, Douglas
Alexander, Lloyd
Alighieri, Dante
Asimov, Isaac
other names deleted for space

1. Sort names by last name
2. Sort names by first name
3. Search for a name
4. Quit

Choose a number: 2
Albert Camus
Aldous Huxley
Angela Carter
Anne Rice
Anne McCaffrey
Anthony Burgess
other names deleted for space

1. Sort names by last name
2. Sort names by first name
3. Search for a name
4. Quit

Choose a number: 3
Search for what? Will
Names matched:
   William S.  Burroughs
   William Shakespeare

1. Sort names by last name
2. Sort names by first name
3. Search for a name
4. Quit

Choose a number: 4
$

Listing 8.1 shows the code for our sorting and searching script:

Listing 8.1. morenames.pl
1:  #!/usr/local/bin/perl -w
2:
3:  %names = ();                    # hash of names
4:  @raw = ();                      # raw words
5:  $fn = "";                       # first name
6:  $in = '';                       # temporary in
7:  @keys = ();                     # temporary keys
8:  @n = ();                        # temporary name
9:  $search = '';                   # thing to search for
10:
11: while (<>) {
12:     chomp;
13:     @raw = split(" ", $_);
14:     if ($#raw == 1) {  # regular case
15:         $names{$raw[1]}  = $raw[0];
16:     }  else {  # build a first name
17:         $fn = "";
18:         for ($i = 0; $i < $#raw; $i++) {
19:             $fn .= $raw[$i] . " ";
20:         }
21:         $names{$raw[$#raw]}  = $fn;
22:     }
23: }
24:
25: while () {
26:     print "
1. Sort names by last name
";
27:     print "2. Sort names by first name
";
28:     print "3. Search for a name
";
29:     print "4. Quit

";
30:     print "Choose a number: ";
31:
32:     if ($in eq '1') {           # sort and print by last name
33:         foreach $name (sort keys %names) {
34:             print "$name, $names{$name} 
";
35:         }
36:
37:     } elsif ($in eq '2') {      # sort and print by first name
38:         @keys = sort { $names{$a}  cmp $names{$b}  }  keys %names;
39:         foreach $name (@keys) {
40:             print "$names{$name}  $name
";
41:         }
42:     } elsif ($in eq '3') {      # find a name (1 or more)
43:         print "Search for what? ";
44:         chomp($search = <STDIN>);
45:
46:         while (@n = each %names) {
47:             if (grep /$search/, @n) {
48:                 $keys[++$#keys] = $n[0];
49:             }
50:         }
51:
52:         if (@keys) {
53:             print "Names matched: 
";
54:             foreach $name (sort @keys) {
55:                 print "   $names{$name}  $name
";
56:             }
57:         }  else {
58:             print "None found.
";
59:         }
60:
61:         @keys = ();  # undefine @keys for next search
62:    } elsif ($in eq '4') {      # quit
63:       last;
64:    } else {
65:       print "Not a good answer.  1 to 4 please.
";
66:    }
67: }
					

The basic framework of this script is the large while loop that manages the choices, from 1 to 4. After printing the choices and prompting for one, the while loop contains a set of statements to test for each answer. If the answer was not 1, 2, 3, or 4 (and we test for the string versions of these numbers, so that an errant letter won't produce warnings), the final else in line 64 will handle that and repeat from the top.

The framework itself is just a bunch of tests and not worth a detailed description. What happens at each option is much more interesting. The two sorting options (options 1 and 2, starting in lines 35 and 41, respectively) each use different forms of the sort function you've learned about previously in this lesson. The first option, to search for something in the names, uses grep to find it.Let's look at the two sorting options first. Our names hash is keyed by last name, so sorting the list by last name is easy. In fact, the foreach loop in line 32 through 37 is the same foreach loop we used for previous versions of this example.

Sorting by first name, the second option involves sorting the hash by the values, which is more difficult. Fortunately, I just showed you how to do this in the section on sorting, so you can use the technique I showed you there in lines 37 through 41. Build a temporary list of the keys with a sort routine that compares the values, and then to use those keys to print out the names in the right order.

Which brings us to the search. We'll start in lines 43 and 44 by prompting for the search key, storing it in the $search variable.

At first glance, you might think doing this search is easy—just use the grep function with the characters the user entered as the pattern. But the catch here is that we're searching a hash, and there are both keys and values to keep track of.

If all we wanted to search was the last names, we could just use the keys function to extract the keys and search them, like this:

@matches = grep /$search/, keys %names;

To get at the values, we could use a foreach loop to loop through the keys, and then test the pattern against both the key and the value in turn. And there would be nothing wrong with that approach, but in this particular example I wanted to use grep, so I took what might look like a rather unusual approach (I prefer to think of it as a creative approach). I used the each function, which you learned about on Day 5, which gives you a list of a key/value pair. With that list, you can then use grep, and if it matches, store the key for printing later.

That's what I'm doing in lines 46 through 50. Let's look at those lines more closely:

46: while (@n = each %names) {
47:     if (grep /$search/, @n) {
48:         $keys[++$#keys] = $n[0];
49:     }
50: }

The each function gives you a two-element list of a key and a value from the hash. Calling each multiple times eventually works its way through all the keys and values in the hash. So, in line 54, this while loop will iterate as many times as there are key/value pairs in the hash, assigning each pair to the list in @n. When there are no more pairs to examine, @n will get the empty list () and the while will stop.

Inside the while loop, we use an if test and grep to test for the search pattern in our simple key/value list. Here we're using grep in a scalar context, so if grep finds anything, it'll return a nonzero number, and the if test will be true. Otherwise, it'll return 0 and we'll skip to the next iteration of the while.

Line 55 is where we store the key if grep was able to find something that matched the search pattern, by simply appending the key to the end of the @keys array. This line is one of those examples of convoluted Perl, so let's look at it more closely:

$keys[++$#keys] = $n[0];

Remember that @n contains two elements, a key and a value. At this point in the script, either the key or the value have matched, so we'll store the key in the @keys array (storing the key will give us access to the value, so we don't have to worry about that). $n[0] gives us that matched key.

Next step is to assign that value to the end of the @keys array. Remember that $#keys gives us the highest index position in the array, so ++$#keys refers to the position just after that last one. Note that the increment is done in prefix notation, so we can increment that element before we assign anything to it. We can then use that new highest index position as the position in which to assign the key.

Whew! This line is one of those examples where you can cram a whole lot of information into a single line, and unless you understand each and every character, it'll take a while to decipher it. Fortunately, if this sort of syntax makes your head spin, there's a much easier way to add an element onto the end of a list, using the push function, which we'll look at later in this lesson.

After the list of matched keys has been built, all that's left is to print them. In this case, we'll test first to see if @keys has any elements (line 60), and if so, the script sorts and prints them. Otherwise, it prints a helpful message (line 66).

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.137.217.134