Perls Before Swine: Custom search

  1. Arrays and functions
  2. Perls Before Swine
  3. Creating files

Remember that search for “stand” that topped the list with a bunch of older artists? Try that search again without asking for a summary and most of them don’t have “stand” anywhere in the song, artist, or album.

The genre for those songs is Standards. Ask for raw format and you’ll see that. Our search is searching through the entire line, both the stuff we can see and the stuff we can’t.

Currently, our script searches everything for the text we specify. It would be nice to be able to focus our search on just the artist, just the album, or just the song. This way, we can search for songs about Yellow without songs about Yellow without picking up albums that mention Yellow.

If we want to search for all songs that mention “yellow” by an artist whose name contains “joni”, we might use:

./show –artist Joni –song yellow songs.txt

The first step to doing this is to add artist, song, and album to the list of switches.

First, make a list of valid fields to search in:

#options for fields to search in
@validFields = ("artist", "album", "song");
$validFields = englishJoin(", ", "and", @validFields);

Second, add another elif to the switches area:

} elsif (grep(/^$switch$/, @validFields)) {
if ($searchText = shift) {
$searches{$switch} = $searchText;
} else {
print "\nSearching in $switch requires text to search on.\n\n";
help();
exit;
}

We’re storing the search text in an associative array whose key is the field we want to search on.

Because we are now going to be doing multiple searches, we’re going to want a subroutine to do the search. Otherwise, we’ll have to duplicate the “if ($sensitive)” lines for each field we want to search on:

sub match {
my($searchIn) = shift;
my($searchFor) = shift;
my($matched) = 0;

if ($sensitive) {
$matched = $searchIn =~ /$searchFor/;
} else {
$matched = $searchIn =~ /$searchFor/i;
}

return $matched;
}

Change “if ($searchFor = shift) {“ to:

if (%searches) {

Instead of expecting some search text, we’re now checking to see if at least one of the searches has been specified. The if block will only be performed if the associative array called “searches” exists and isn’t empty.

And finally, replace the “if ($sensitive)” blocks with:

foreach $searchField (keys %searches) {
$needle = $searches{$searchField};
$haystack = $$searchField;
$matched = match($haystack, $needle);
last if !$matched;
}

Go to the command line and type:

./show --album yellow --song girl songs.txt

You should get back three songs. The albums “Mellow Yellow” and “Goodbye Yellow Brick Road” both contain at least one song whose title contains “yellow”.

First, we assign the number ‘1’ to the variable $matched. By default, we’re assuming that we found a match.

Next, we loop through each field for which we want to search for text. For each such field:

1. We pull the text we’re looking for back out of the “searches” associative array, and assign that text to the variable $needle.

2. We grab the haystack—the text of the current field, that we want to search through, through a little trick called dereferencing a symbolic reference. Imagine that we are searching for an artist. The %searches array contains “artist” as the key and “some text” as the value. So, $searchField will be “artist”. Now, look up above and see that we have a variable called $artist. If $searchField is “artist”, then $$searchField is the same as $artist. So when we say $haystack = $$searchField, this is the same as saying $haystack = $artist.

3. We set $matched to whether or not $needle can be found in $haystack. If the needle can’t be found, $matched will be false.

4. If $matched is false, there is no need to go any further, so the last line exits if !$matched.

5. At the end of this loop, $matched is either true or false. If it is true, this track matched our search. Otherwise it did not. It failed at least one of the searches requested on the command line.

If $matched can go through all three checks without becoming zero, that means that this song matches our search. Remember that some checks will be skipped, and thus not affect $matched.

Go ahead and play around with some searches. You can find all of the Elton John songs about girls on albums about yellow, with:

./show --album yellow --song girl --artist "Elton John" songs.txt

All of the Elton John songs about girls can be found with:

./show --song girl --artist "Elton John" songs.txt

And, of course, don’t forget to add a line to the help for this item! You’ll need to change the top item:

print "Syntax: show [options] [song files]\n";

And add a few lines to the bottom:

print "\t--$validFields <searchtext>: search in the $validFields field\n";
print "At least one of the search requests must be specified.\n";

That’s it!

Symbolic references can be taken to any level. If $key contains “artist”, $artist contains “Baez”, and $Baez contains “Joan”, then $$key is the same as $artist which is the same as “Baez”. $$$key is the same as $$artist which is the same as $Baez which is the same as “Joan”. Symbolic references are a powerful tool, but can easily make your script confusing. Use them carefully.

  1. Arrays and functions
  2. Perls Before Swine
  3. Creating files