Answers to Chapter 15 Exercises
- Here's one way to do it:
my @numbers; push @numbers, split while <>; foreach (sort { $a <=> $b } @numbers) { printf "%20g\n", $_; }That second line of code is too confusing, isn't it? Well, we did that on purpose. Although we recommend that you write clear code, some people like writing code that's as hard to understand as possible,[398] so we want you to be prepared for the worst. Someday, you'll need to maintain confusing code like this.
[398]Well, we don't recommend it for normal coding purposes, but it can be a fun game to write confusing code, and it can be educational to take someone else's obfuscated code examples and spend a weekend or two figuring out just what they do. If you want to see some fun snippets of such code and maybe get a little help with decoding them, ask around at the next Perl Mongers' meeting. Or search for JAPHs on the Web, or see how well you can decipher the example obfuscated code block near the end of this chapter's answers.
Since that line uses the
whilemodifier, it's the same as if it were written in a loop like this:while (<>) { push @numbers, split; }That's better, but maybe it's still a little unclear. (Nevertheless, we don't have a quibble about writing it this way. This one is on the correct side of the "too hard to understand at a glance" line.) The
whileloop is reading the input a line at a time (from the user's choice of input sources, as shown by the diamond operator), andsplitis, by default, splitting that on whitespace to make a list of words -- or, in this case, a list of numbers. The input is just a stream of numbers separated by whitespace, after all. Either way that you write it, then, thatwhileloop will put all of the numbers from the input into@numbers.The
foreachloop takes the sorted list and prints each one on its own line, using the%20gnumeric format to put them in a right-justified column. You could have used%20sinstead. What difference would that make? Well, that's a string format, so it would have left the strings untouched in the output. Did you notice that our sample data included both and , and both and ? If you printed those as strings, the extra zero characters will still be in the output; but%20gis a numeric format, so equal numbers will appear identically in the output. Either format could potentially be correct, depending upon what you're trying to do. - Here's one way to do it:
# don't forget to incorporate the hash %last_name, # either from the exercise text or the downloaded file my @keys = sort { "\L$last_name{$a}" cmp "\L$last_name{$b}" # by last name or "\L$a" cmp "\L$b" # by first name } keys %last_name; foreach (@keys) { print "$last_name{$_}, $_\n"; # Rubble,Bamm-Bamm }There's not much to say about this one; we put the keys in order as needed, then print them out. We chose to print them in last-name-comma-first-name order just for fun; the exercise description left that up to you.
- Here's one way to do it:
print "Please enter a string: "; chomp(my $string = <STDIN>); print "Please enter a substring: "; chomp(my $sub = <STDIN>); my @places; for (my $pos = -1; ; ) { # tricky use of three-part for loop $pos = index($string, $sub, $pos + 1); # find next position last if $pos == -1; push @places, $pos; } print "Locations of '$sub' in '$string' were: @places\n";This one starts out simply enough, asking the user for the strings and declaring an array to hold the list of substring positions. But once again, as we see in the
forloop, the code seems to have been "optimized for cleverness", which should be done only for fun, never in production code. But this actually shows a valid technique which could be useful in some cases, so let's see how it works.The
myvariable$posis declared private to the scope of theforloop, and it starts with a value of-1. So as not to keep you in suspense about this variable, we'll tell you right now that it's going to hold a position of the substring in the larger string. The test and increment sections of theforloop are empty, so this is an infinite loop. (Of course, we'll eventually break out of it, in this case withlast).The first statement of the loop body looks for the first occurrence of the substring at or after position
$pos + 1. That means that on the first iteration, when$posis still-1, the search will start at position , the start of the string. The location of the substring is stored back into$pos. Now, if that was-1, we're done with theforloop, solastbreaks out of the loop in that case. If it wasn't-1, then we save the position into@placesand go around the loop again. This time,$pos + 1means that we'll start looking for the substring just after the previous place where we found it. And so we get the answers we wanted and the world is once again a happy place.If you didn't want that tricky use of the
forloop, you could accomplish the same result as shown here:{ my $pos = -1; while (1) { ... # Same loop body as the for loop used above } }The naked block on the outside restricts the scope of
$pos. You don't have to do that, but it's often a good idea to declare each variable in the smallest possible scope. This means we have fewer variables "alive" at any given point in the program, making it less likely that we'll accidentally reuse the name$posfor some new purpose. For the same reason, if you don't declare a variable in a small scope, you should generally give it a longer name that's thereby less likely to be reused by accident. Maybe something like$substring_positionwould be appropriate in this case.On the other hand, if you were trying to obfuscate your code (shame on you!), you could create a monster like this (shame on us!):
for (my $pos = -1; -1 != ($pos = index +$string, +$sub, +$pos +1 ); push @places, ((((+$pos))))) { 'for ($pos != 1; # ;$pos++) { print "position $pos\n";#;';#' } pop @places; }That even trickier code works in place of the original tricky
forloop. By now, you should know enough to be able to decipher that one on your own, or to obfuscate code in order to amaze your friends and confound your enemies. Be sure to use these powers only for good, never for evil.Oh, and what did you get when you searched for
tinThis is a test.? It's at positions and . It's not at position ; since the capitalization doesn't match, the substring doesn't match.