
Perl solution to Event 2 in the 2008 Winter Scripting Games.
Solutions are also available for VBScript and Windows PowerShell.
We have to tell you the truth here: the Scripting Guys are lazy. On top of that, we had a lot to do in order to get ready for the Scripting Games. Therefore, whenever possible, we took the easy way out. Take these Scripting Games solutions, for example. We typically start out by writing the VBScript solution for the Games; that’s because we’re more familiar with VBScript, and because VBScript is still the language of choice for a good many Script Center patrons. Once we have a solution that works in VBScript we then port that solution to Windows PowerShell and Perl. That makes it easy for us to write our PowerShell and Perl versions; however, it also means that we don’t always take full advantage of the capabilities of PowerShell and Perl. (Which, of course, is one reason why we recruited experts to help us out with PowerShell and Perl solutions.)
Event 2 turned out to be a little different. We tried to port our VBScript script (which relied on a disconnected recordset) to Perl, but it didn’t work: we couldn’t figure out how to sort a disconnected recordset. That turned out to be a good thing, and for two reasons: 1) it enabled us to discover a problem with the VBScript and PowerShell solutions (see the VBScript solution for details); and, 2) it forced us to come up with a Perl solution that was perhaps a little more Perl-like. In other words, a solution that looks like this:
%dictionary = ();
open (SkaterScores, "C:\\Scripts\\Skaters.txt");
@arrSkaters = <SkaterScores>;
close (SkaterScores);
foreach $strSkater (@arrSkaters)
{
$intTotal = 0;
@arrScores = split(",",$strSkater);
$strName = @arrScores[0];
@arrScoresOnly = (@arrScores[1],@arrScores[2],@arrScores[3],@arrScores[4],@arrScores[5],@arrScores[6],@arrScores[7]);
@arrScoresOnly = sort({$a <=> $b} @arrScoresOnly);
$intTotal = @arrScoresOnly[1] + @arrScoresOnly[2] + @arrScoresOnly[3] + @arrScoresOnly[4] + @arrScoresOnly[5];
$dblAverage = $intTotal / 5;
$dictionary{$strName} = $dblAverage;
}
$i = 0;
foreach (sort {$dictionary{$b} <=> $dictionary{$a} } keys %dictionary)
{
$i++;
print "$_, $dictionary{$_}\n";
if ($i == 3) {exit}
}
Like all good figure skating judges, we kicked off our efforts by creating an empty hash table (similar to a Dictionary object in VBScript). That’s what this line of code is for:
%dictionary = ();
After creating the hash table we then use this block of code to open and read the file C:\Scripts\Skaters.txt:
open (SkaterScores, "C:\\Scripts\\Skaters.txt"); @arrSkaters = <SkaterScores>; close (SkaterScores);
As you can see, in the first line we call the open function, passing this function two parameters: SkaterScores, a reference to the text file; and C:\\Scripts\\Skaters.txt, the actual path to the text file. (Why the \\’s in the path? Well, as it turns out, the \ is a reserved character in Perl; that means that each \ must be “escaped” by prefacing it with another \.) In line 2, we read in the file, with each line in that file becoming an item in the array @arrSkaters. Finally, in line 3, we use the close function to close the file.
At this point, believe it or not, we’re ready to start calculating scores.
To do that, we first set up a foreach loop that enables us to walk through all the items in the array (that is, all the skaters and their scores). Inside the loop, we assign the value 0 to a counter variable named $intTotal, then use this line of code (and the split function) to convert the information for the first skater into a mini-array named @arrScores:
@arrScores = split(",",$strSkater);
Why do we do that? Well, as you might recall, the information for each skater looks something like this:
Janko Cajhen,84,80,61,81,71,62,76
We need to throw out the high and low score for this skater, which means that we first need to figure out which scores are the high and low scores for this skater. That’s difficult to do with a one long, comma-separated string value; it’s much easier to do this if we convert this string to an array containing the following items:
Janko Cajhen 84 80 61 81 71 62 76
After grabbing the first item in the array (item 0, Janko Cajhen) and assigning it to a variable named $strName we then use this line of code to create a new array (@arrScoresOnly) consisting of all the scores received by the skater (and only the scores received by the skater):
@arrScoresOnly = (@arrScores[1],@arrScores[2],@arrScores[3],@arrScores[4],@arrScores[5],@arrScores[6],@arrScores[7]);
What’s the point of that? Well, now we can sort the scores using the following line of code:
@arrScoresOnly = sort({$a <=> $b} @arrScoresOnly);
And what’s the point of that? Well, now we have the scores given to this skater, sorted in numeric order:
61 62 71 76 80 81 84
In turn, that makes it really easy to throw out the high and low scores; in this line of code, we take the middle 5 scores (ignoring the high and the low scores), add them together, and assign the sum to the variable $intTotal:
$intTotal = @arrScoresOnly[1] + @arrScoresOnly[2] + @arrScoresOnly[3] + @arrScoresOnly[4] + @arrScoresOnly[5];
That’s pretty good, but we’re not quite there yet. According to the rules, the final score given to a skater involves throwing out the high and low scores (done); summing the remaining 5 scores (done); and then dividing the total points by the number of scores (not done). In order to perform that last step (which simply enables us to calculate the average score for the skater) all we have to do is divide the total score by 5:
$dblAverage = $intTotal / 5;
Once we’ve done that we add the skater’s name and total score to the hash table; that’s what we do here:
$dictionary{$strName} = $dblAverage;
And then it’s back to the top of the loop, where we repeat the process with the next item in the array (or, more precisely, the next skater in the competition.)
At this point we simply need to pick out, and report back, the three highest scores. To be honest, this was also the point where our inexperience with Perl betrayed us a little. We weren’t sure how to permanently sort a hash table, so we did the next best thing: we temporarily sorted the hash table by skater score (in descending order, meaning the highest value comes first and the lowest value comes last) and then simply echoed back the first three items. That’s what this block of code does:
foreach (sort {$dictionary{$b} <=> $dictionary{$a} } keys %dictionary)
{
$i++;
print "$_, $dictionary{$_}\n";
if ($i == 3) {exit}
}
In the first line, we set up a foreach loop that uses the sort function to sort the values in the hash table in descending order; that’s what the construction {$dictionary{$b} <=> $dictionary{$a} is for. We increment the counter variable $i by 1 (a variable we previously set to 0), then use this line of code to print out the key ($_) and value ($dictionary{$_}) for the first item in the hash table:
print "$_, $dictionary{$_}\n";
We then check to see if $i is equal to 3:
if ($i == 3) {exit}
If it is, that means we’ve already printed out the top three scores; in that case, we call the exit function to terminate the script. If not, then we go back to the top of the loop and print out the next item in the hash table.
And what do we end up with when we’re done? This:
Guido Chuffart, 88.2 Jack Creasey, 85.8 Cecilia Cornejo, 85.4
Ladies and gentlemen, your figure skaters medalists.