Arrays and functions: A smarter join

  1. Sort numerically
  2. Arrays and functions
  3. Format conversions

Go back and ask for some format that doesn’t exist:

./show --format wriggling stand songs.txt

Format must be raw, simple, summary.

That should really be raw, simple, or summary. It’s grating to read otherwise. We can make our own subroutine that joins lists together but accepts a conjunction as well as a simple separator.

sub englishJoin {
my($punctuation) = shift;
my($conjunction) = shift;
my(@items) = @_;

my($joined, $finalItem);

if ($#items == -1) {
$joined = "";
} elsif ($#items == 0) {
$joined = $items[0];
} elsif ($#items == 1) {
$joined = "$items[0] $conjunction $items[1]";
} else {
$finalItem = pop(@items);
$joined = join($punctuation, @items) . "$punctuation$conjunction $finalItem";
}

return $joined;
}

This subroutine is expecting that the first parameter it gets is the punctuation (the comma, in our case), the second item it gets is the conjunction (“or”), and the rest of the items is the list that needs to be joined. The symbols @_ in a subroutine mean the list of parameter the subroutine has received, much like @ARGV means the list of command-line arguments. Inside of a subroutine, shift automatically shifts items out of @_ instead of @ARGV.

Subroutines, by default, have access to all of the variables that the script uses. We used this to our advantage in the byArtistCount sort script. However, most of the time we want to make sure that the variables we use in a subroutine don’t accidentally clobber the other variables used in the script.

Any variable inside of a my() is “local” to the current subroutine. If another variable outside of the subroutine has the same name, that other variable won’t affect the “my” variable, and the “my” variable won’t affect that wider variable.

It is always a good idea to automatically “my” any variables a subroutine uses, unless you specifically want to be referencing outside variables.

The characters “$#” in front of a variable name count up the number of items in that array. More specifically, it gives you the current highest item in that simple array. If the array currently has three items in it, the current highest item number is 2, and that’s what “$#” will give you. If the array has one item in it, the current highest item number is 0, and that’s what “$#” will give you.

So we have different if blocks depending on whether there are no items in the list (negative one), one item, two items, or three or more items.

Instead of using “eq” to check what $#items is equal to, we are using two equal signs. Perl uses “eq” and “ne” for comparing text. It uses “==” and “!=” for comparing numbers. This is important because Perl doesn’t care whether a variable is text or is a number until you ask it to make the comparison. Go back to your “compare” script and type:

./compare 10 2

You should get:

Text compare: -1

Number compare: 1

Alphabetically, 10 comes before 2. Numerically, 2 comes before 10. With a text compare “10.0” will not equal “10”. But numerically, 10.0 will equal 10. Use the correct operator depending on whether you want to compare as text or compare as a number.

Here, we are comparing as numbers.

The final “else” has a few new things in it also. The pop function is the same as shift except that it takes an item off of the end of the array instead of the beginning.

Those are periods between the “join(…)” function and the text in quotes. If you want to add two numbers together, you use “+”. But if you want to add two strings to each other you use a period. This is also sometimes called concatenation.

Change

$validFormats = join(", ", @validFormats);

to

$validFormats = englishJoin(", ", "or", @validFormats);

And now:

./show --format wriggling stand songs.txt

Format must be raw, simple, or summary.

So, now it works, and it will work for any future formats that we add. We also have a new subroutine available if we need a more readable join for any list.

  1. Sort numerically
  2. Arrays and functions
  3. Format conversions