February 2008 Archives
...because the second leg (ORD->LGA) of our outgoing New York trip flights has been cancelled, and I wish to know whether I should feel sympathetic towards the airline (American) - if, say, their plane is on fire or their pilot has exploded - or whether I should want to set them on fire - if, say, they just decided to walk on their obligations to save money.
Not knowing which scenario I should emote for tasks me, it truly does.
So, if you happen to be running Exchange 2007 on your Windows 2003, it is no longer upgradable in-place to Windows 2008. Instead, you must get another server, install Windows 2008 on that one, install Exchange 2007 on top of it, move all your mailboxes, etc., reformat the first server, install a clean copy of Windows 2008 on that, and assuming you want to go back to having Exchange on it rather than on the second server, repeat the middle half of this whole wretched procedure. Add assorted aids, incidents, and inconveniences if you happen - like virtually everyone except large corporations with money to burn - to have anything on your Exchange server besides Exchange.
Dear Exchange/Windows teams: what the bloody fucking hell did you think you were doing when you shipped these products in this state? And more to the point, who, pray, is going to whip up a nice shiny-new x64 server for me to use to do it?
...that you think might underly some of the claims your ideological opponents might be making.
But then you think, no. Surely not. No-one would be so manifestly dimwitted as to actually claim that inconsistency/incoherency in your belief-system is a virtue, would they?
Seen in a comment thread:
Even wingnuts can have good ideas once in a while. It's just that they tend to try to tie it into their general theory of everything.
Guess I was wrong.
When I, a single person, have a list of tasks I wish to accomplish, each with its set priority, I am constrained by my singularity to execute those tasks serially, ranked in priority order.
When an organization of thousands of people has a list of tasks it wishes to accomplish, each with its set priority, its multiplicity permits it to execute those tasks in parallel, ranked in priority order.
When Brooks's Law of headcount-time is taken into account ("you can't have a baby in one month by assigning nine women to the job"), it is very likely that lower-priority tasks will execute in parallel to higher-priority tasks, once the higher-priority tasks have reached optimal headcount.
In a sufficiently large organization, the combination of these ensure that even trivia will get some headcount-time.
Pretending you don't know this, or claiming misprioritization on something you think shouldn't be done at all, thus mainly succeeds in making you look very, very stupid. Especially if you have to manage any team larger than just yourself, and thus therefore should be familiar with this.
That is all.
As usual, scenario here - test a password for strength using supplied criteria - with official PowerShell solution here and Perl solution here.
[Note: I'd also say that I personally would use different password strength criteria in some cases, if you're looking for a password checking script specifically and Google has brought you here. For one thing, a good password should be a passphrase, and thus much longer than 20 characters.]
My PowerShell solution:
# Advanced Division, Event 5: You Call That A Strong Password?
# wordlist file
$wordlistFile = 'C:\Scripts\wordlist.txt'
# initial password score
$score = 13
# Read in the password from the command line.
$password = $args[0]
Write-Host ""
# Load the word-list.
# Pull in the word list into an appropriate collection.
# Words in the list are mixed-case; fold them all to uppercase.
$wordlist = New-Object System.Collections.ArrayList
Get-Content $wordlistFile | ForEach-Object {
$null = $wordlist.Add($_.ToUpper())
}
# Check One: Not an actual word?
$actual = $FALSE
if ($wordlist.Contains($password.ToUpper()))
{
Write-Host "Password is a dictionary word."
$score--
$actual = $TRUE
}
# Check Two: Minus last letter, still not an actual word?
if ($wordlist.Contains(($password.Remove($password.Length - 1, 1)).ToUpper()))
{
Write-Host "Password, except for the last letter, is a dictionary word."
$score--
}
# Check Three: Minus first letter, still not an actual word?
if ($wordlist.Contains(($password.Remove(0, 1)).ToUpper()))
{
Write-Host "Password, except for the first letter, is a dictionary word."
$score--
}
# Check Four: Check 0 not substituted for O (either case).
$test = $password.Replace("0", "O") ;
if (($wordlist.Contains($test.ToUpper())) -and !$actual)
{
Write-Host "Password is a dictionary word with 0 substituted for O."
$score--
}
# Check Five: Check 1 not substituted for L (either case).
$test = $password.Replace("1", "L") ;
if (($wordlist.Contains($test.ToUpper())) -and !$actual)
{
Write-Host "Password is a dictionary word with 1 substituted for L."
$score--
}
# Check Six: At least 10 characters long; no more than 20 characters long.
if ($password.Length -lt 10)
{
Write-Host "Password is shorter than 10 characters."
$score --
}
if ($password.Length -gt 20)
{
Write-Host "Password is longer than 20 characters."
$score--
}
# Check Seven: Includes at least one number (digits 0 to 9)
if ($password -notmatch "[0-9]")
{
Write-Host "Password does not contain at least one number."
$score--
}
# Check Eight: Includes at least one uppercase letter.
if ($password -cnotmatch "[A-Z]")
{
Write-Host "Password does not contain at least one uppercase letter."
$score--
}
# Check Nine: Includes at least one lowercase letter.
if ($password -cnotmatch "[a-z]")
{
Write-Host "Password does not contain at least one lowercase letter."
$score--
}
# Check Ten: Includes at least one symbol.
if ($password -notmatch "[^0-9A-Z]")
{
Write-Host "Password does not contain at least one symbol."
$score--
}
# Check Eleven: Does not include four or more lowercase letters in succession.
if ($password -cmatch "[a-z]{4}")
{
Write-Host "Password contains four successive lowercase letters."
$score--
}
# Check Twelve: Does not include four or more uppercase letters in succession.
if ($password -cmatch "[A-Z]{4}")
{
Write-Host "Password contains four successive lowercase letters."
$score--
}
# Check Thirteen: Does not include any duplicate characters.
$dupe = $FALSE
for ($i = 0; $i -lt $password.Length - 1; $i++)
{
for ($j = $i + 1; $j -lt $password.Length; $j++)
{
if ($password[$i] -eq $password[$j])
{
$dupe = $TRUE
}
}
}
if ($dupe)
{
Write-Host "Password contains duplicate characters."
$score--
}
Write-Host ""
# Display the password strength.
if ($score -le 6)
{
$rating = "weak"
}
elseif ($score -le 10)
{
$rating = "moderately-strong"
}
else
{
$rating = "strong"
}
Write-Host "A password score of" $score "indicates a" $rating "password.`n"
I have to say, I think mine is cleaner.
And my essentially identical Perl solution:
#!C:\Perl\bin\Perl.exe use strict; use warnings; # Advanced Division, Event 5: You Call That A Strong Password? # wordlist file my $wordlistFile = 'C:/Scripts/wordlist.txt' ; # initial password score my $score = 13 ; # Read in the password from the command line. my $password = $ARGV[0] ; print "\n" ; # Load the word-list. # Pull in the word list into an appropriate collection. # Words in the list are mixed-case; fold them all to uppercase. open WORDS, $wordlistFile ; my %wordlist ; while () { chomp ; $wordlist{uc($_)} = 1; } close WORDS ; # Check One: Not an actual word? my $actual = 0 ; if (exists ($wordlist{uc ($password)})) { print "Password is a dictionary word.\n" ; $score-- ; $actual = 1 ; } # Check Two: Minus last letter, still not an actual word? if (exists ($wordlist{uc (substr ($password, 0, length ($password) - 1))})) { print "Password, except for the last letter, is a dictionary word.\n" ; $score-- ; } # Check Three: Minus first letter, still not an actual word? if (exists ($wordlist{uc (substr ($password, 1, length ($password)))})) { print "Password, except for the first letter, is a dictionary word.\n" ; $score-- ; } # Check Four: Check 0 not substituted for O (either case). { my $test = $password ; $test =~ s/0/O/g ; if ((exists ($wordlist{uc ($test)})) && (! $actual)) { print "Password is a dictionary word with 0 substituted for O.\n" ; $score-- ; } } # Check Five: Check 1 not substituted for L (either case). { my $test = $password ; $test =~ s/1/L/g ; if ((exists ($wordlist{uc ($test)})) && (! $actual)) { print "Password is a dictionary word with 1 substituted for L.\n" ; $score-- ; } } # Check Six: At least 10 characters long; no more than 20 characters long. if (length ($password) < 10) { print "Password is shorter than 10 characters.\n" ; $score-- ; } if (length ($password) > 20) { print "Password is longer than 20 characters.\n" ; $score-- ; } # Check Seven: Includes at least one number (digits 0 to 9) if ($password !~ /[0-9]/) { print "Password does not contain at least one number.\n" ; $score-- ; } # Check Eight: Includes at least one uppercase letter. if ($password !~ /[A-Z]/) { print "Password does not contain at least one uppercase letter.\n" ; $score-- ; } # Check Nine: Includes at least one lowercase letter. if ($password !~ /[a-z]/) { print "Password does not contain at least one lowercase letter.\n" ; $score-- ; } # Check Ten: Includes at least one symbol. # (perl is default case-sensitive) if ($password !~ /[^0-9A-Za-z]/) { print "Password does not contain at least one symbol.\n" ; $score-- ; } # Check Eleven: Does not include four or more lowercase letters in succession. if ($password =~ /[a-z]{4}/) { print "Password contains four successive lowercase letters.\n" ; $score-- ; } # Check Twelve: Does not include four or more uppercase letters in succession. if ($password =~ /[A-Z]{4}/) { print "Password contains four successive uppercase letters.\n" ; $score-- ; } # Check Thirteen: Does not include any duplicate characters. my $dupe = 0 ; for (my $i = 0; $i < length ($password) - 1; $i++) { for (my $j = $i + 1; $j < length ($password); $j++) { if (substr ($password, $i, 1) eq substr ($password, $j, 1)) { $dupe = 1 } } } if ($dupe) { print "Password contains duplicate characters.\n" ; $score-- ; } print "\n" ; # Display the password strength. my $rating; if ($score <= 6) { $rating = "weak" ; } elsif ($score <= 10) { $rating = "moderately-strong" ; } else { $rating = "strong" ; } print "A password score of $score indicates a $rating password.\n\n" ;
As usual, scenario here - finding the days, months, and months/days between two dates - and official PowerShell solution here, and official Perl solution here. My PowerShell solution:
# Beginner Event 5: What's the Difference?
$dateString = $args[0]
# Parses most kinds of date, including the kind that the instructions
# for this event tell us we get. If it can't parse it, throws, which
# causes the shell to display an error, which is what we want anyway.
$date = [System.DateTime]::Parse($dateString)
# Get today's date.
$today = [System.DateTime]::Today
# We also get to assume that the date is after the current date,
# but it wouldn't hurt to check.
if ($today -gt $date)
{
Throw "Date given is before today's date"
}
$rawdays = ($date - $today).Days
# Can't use timespan math to fulfil these conditions.
$months = $date.Month - $today.Month
$months += (($date.Year - $today.Year) * 12)
$rawmonths = $months
$days = $date.Day - $today.Day
if ($days -lt 0)
{
# Days are less than zero.
# Months decreases by one, since a whole month has not passed.
$months--
# Days are equal to the number of days in the previous month,
# to $months, plus (minus, effectively) Days.
# Easiest way to get there... take the first day of the final month
# and substract a day to get the last day of the previous month, whose
# day number obviously is equal to the number of days in that month
#
# This also handles leap years automagically.
$temp = new DateTime $date.Year, $date.Month, 1
$temp = $temp.AddDays(-1)
$days = $temp.Day + $days
}
else
{
# Days are greater than zero, indicating that additional time has passed
# from the same day in the given month; $days stays the same.
}
Write-Host "Days: " $rawdays
Write-Host "Months: " $rawmonths
Write-Host "Months/Days:" $months "/" $days
And this is where I pretty much admit that I forgot the existence of the get-date cmdlet completely, although [System.DateTime]::Today does essentially the same job. And I do suppose [datetime]$args[0] is shorter than explicitly calling the Parse method...
And my Perl solution:
#!C:\Perl\bin\perl.exe
use strict;
use warnings;
# Beginner Event 5: What's the Difference?
use Date::Calc qw(Days_in_Month Decode_Date_US Delta_Days Delta_YMD Today) ;
my $inputyear;
my $inputmonth;
my $inputday ;
if (!(($inputyear, $inputmonth, $inputday) = Decode_Date_US($ARGV[0])))
{
die "Could not parse date" ;
}
# Get today's date.
my $nowyear;
my $nowmonth;
my $nowday;
($nowyear, $nowmonth, $nowday) = Today() ;
my $rawdays = Delta_Days ($nowyear, $nowmonth, $nowday,
$inputyear, $inputmonth, $inputday) ;
# We also get to assume that the date is after the current date,
# but it wouldn't hurt to check.
if ($rawdays < 0)
{
die "Date given is before today's date" ;
}
my $dyear ;
my $dmonth;
my $dday;
($dyear, $dmonth, $dday) = Delta_YMD ($nowyear, $nowmonth, $nowday,
$inputyear, $inputmonth, $inputday) ;
my $dmonths = $dmonth + ($dyear * 12) ;
my $rawmonths = $dmonths;
if ($dday < 0)
{
# Days are less than zero.
# Days are equal to the number of days in the previous month,
# to $months, plus (minus, effectively) Days.
my $previousMonthDays = Days_in_Month ($inputyear, $inputmonth -1) ;
$dday = $previousMonthDays + $dday ;
# subtraction puts us in the previous month
$dmonths-- ;
}
else
{
# Days are greater than zero, indicating that additional time has passed
# from the same day in the given month; $days stays the same.
}
print "Days: $rawdays\n" ;
print "Months: $rawmonths\n" ;
print "Months/Days: $dmonths / $dday\n" ;
Very similar, only using Date::Calc functions to substitute for the DateTime class functionality in .NET.
...last night.
A 5.3, even. Good grief. That sort of thing never happens, except for just then, obviously. And that other time. But still. Unusual.
By most, or at least many, reasonable standards, this should have been a terrible book.
I mean, just look at the elements. We have, let's see, the orphan thief (well, actually, several); the tight-knit thieves organization; the city built on the ruins of the older and better civilisation; the fallen Empire in the background; the... well, I could go on. By rights, given the number of what might be considered awfully cliche elements, one might be forgiven for expecting The Lies of Locke Lamora to plunge headlong into the EFP hackery category.
Which is why I am delightedly surprised to be noting all those things in retrospect, after spending the last few nights devouring this page-turner as fast as the demands of work and sleep permitted. While he may be assembling existing parts, Mr. Lynch does so awfully effectively, pulling one in with the basic caper tale and the bits and pieces of Locke's past we see in the inter-chapter interludes to start out with, and thus making sure one's thoroughly hooked by the time the major plot hoves into view. And then it delivers.
Recommended, and I'll take another one as soon as it can be shipped here, please?
...the webcomic, that is, is so very much better than the actual Star Wars: Episode 1.
Enjoy.
(It will, of course, be interesting to see how that persists through the other five movies.)
Scenario - finding WMI properties that begin with every letter from A to Y - here, official solution here. Mine follows:
# Sudden Death Challenge, Day 5
# get all the "win32" classes in the "root/civm2" namespace
$win32wmi = (get-wmiobject -namespace "root/cimv2" -list | Where-Object { $_.Name -like "Win32_*" })
$properties = New-Object System.Collections.Hashtable
# get all the properties belonging to those classes
foreach ($class in $win32wmi)
{
$props = ($class.CreateInstance() | get-member -memberType Property | ForEach-Object { $_.Name.ToString() })
foreach ($prop in $props)
{
if (! $properties.Contains($prop))
{
$properties.Add($prop, $class.Name)
# write-host $class ":" $prop
}
}
}
# foreach letter from A to Y, search
for ($letterv = [char]'A' ; $letterv -le [int][char]'Y' ; ([int]$letterv)++)
{
$letter = ([char][int]$letterv)
foreach ($prop in $properties.Keys)
{
if ($prop[0] -eq $letter)
{
$spaces = 40 - $prop.length
write-host $prop (" " * $spaces) $properties[$prop]
break
}
}
}
Of course, this is insanely cache-y, but what the hell.
And as I check my referrer logs to see what caused a minor blip in traffic, recently, I see that Michi (that's "michiexile" for those of you on LJ) has given us the solution to the first of the Scripting Games events in Haskell.
(And I see one of his commenters has given us the Python, too.)
I remember Haskell from my university days. I really should play with it again.
As usual, scenario here - creating and printing a calendar for a given month - and official solution here for PowerShell and here for Perl. My PowerShell solution follows:
# Advanced Event 4: Image is Everything
# printing the calendar; get a month and year from the user
$rawdate = Read-Host -prompt "Please enter a month and year in the format (mm/yyyy)"
# validate the entered date
if (($rawdate -notmatch "^[0-9]/[0-9][0-9][0-9][0-9]$") -and ($rawdate -notmatch "^[0-1][0-9]/[0-9][0-9][0-
9][0-9]$"))
{
Throw "Not in the specified format."
}
$month, $year = $rawdate.Split('/')
# create a new DateTime for the first of the specified month, for use in
# later calculations
$date = new System.DateTime $year, $month, 1
# print back the month and year, as confirmation
Write-Host ("`n`n" + $date.ToString("MMMM yyyy") + "`n")
# print a nicely formatted days-of-week strip, and space
Write-Host @"
Sun Mon Tue Wed Thu Fri Sat
"@
# find the place to start, and iterate until done
$column = [int32]$date.DayOfWeek
Write-Host -nonewline ("`t" * $column)
# while in the same month...
while ($date.Month -eq $month)
{
# write the date
Write-Host -nonewline ("{0,3}" -f [int32]$date.Day)
if ($column -eq 6)
{
# if we're in the last column, start a new line
Write-Host ""
$column = 0
}
else
{
# if we're not in the last column, advance to the next
Write-Host -nonewline "`t"
$column++
}
$date = $date.AddDays(1)
}
Write-Host "`n`n"
And here, my essentially identical Perl solution:
#!C:\Perl\bin\perl.exe use strict ; # Advanced Event 4: Image is Everything use Date::Calc qw(Month_to_Text Day_of_Week Days_in_Month); # printing the calendar; get a month and year from the user print "Please enter a month and year in the format (mm/yyyy): " ; my $rawdate =; chomp $rawdate ; # validate the entered date if (($rawdate !~ "^[0-9]/[0-9][0-9][0-9][0-9]\$") && ($rawdate !~ "^[0-1][0-9]/[0-9][0-9][0-9][0-9]\$")) { die "Not in the specified format." ; } (my $month, my $year) = split (/\//, $rawdate) ; # as we're not going to stuff it into a .NET DateTime, we have to also # validate the month. if ($month > 12) { die "Not a valid month." ; } # print back the month and year, as confirmation my $monthname = Month_to_Text($month) ; print "\n\n$monthname $year\n" ; # print a nicely formatted days-of-week strip, and space print "\nSun\tMon\tTue\tWed\tThu\tFri\tSat\n\n" ; # find the place to start, and iterate until done my $column = Day_of_Week($year, $month, 1); # we want Sunday on the left if ($column == 7) { $column == 0; } print ("\t" x $column) ; # while in the same month... my $lastday = Days_in_Month($year, $month) ; for (my $day = 1; $day <= $lastday; $day++) { # write the date print sprintf ("%3d", $day) ; if ($column == 6) { # if we're in the last column, start a new line print "\n" ; $column = 0 ; } else { # if we're not in the last column, advance to the next print "\t" ; $column++ ; } } print "\n\n" ;
As usual, scenario here - computing the winner of an instant run-off election - and official solutions here for PowerShell and here for Perl. My PowerShell solution follows:
# Advanced Event 3: Instant (Runoff) Winner
# vote file
$voteFile = 'C:\Scripts\votes.txt'
# Okay, here's how we're storing the votes; essentially, each person's ballot
# of votes is transformed into an ordered arraylist, highest to lowest; we then
# stash all these ordered arrays into the $votes array.
#
# Since we count first choices (modulo removed votes on subsequent iterations),
# we just walk down $votes tallying up the [0] element of each sub-array.
#
# When it's time to remove a candidate, we iterate the $votes array and each
# sub-array inside it finding and removing that candidate. It should be
# impossible to remove all the items from any given ballot before we get
# a result, so we don't need to check for that when we're counting.
# votes (overall)
$votes = New-Object System.Collections.ArrayList
# first, we suck in the votes
foreach ($line in (Get-Content $voteFile))
{
$votearray = $line.Split(',')
$ballot = New-Object System.Collections.ArrayList (,$votearray)
$null = $votes.Add($ballot) ;
}
$ballots = $votes.Count
# counting routine
while ($TRUE)
{
# DEBUG: $iteration++
# DEBUG: Write-Host "iteration:" $iteration
# iterate each ballot's first choice and count them
$counts = New-Object System.Collections.Hashtable
foreach ($ballot in $votes)
{
# DEBUG: Write-Host "ballot:" $ballot.Count
$counts[$ballot[0]]++
}
# Iterate the counts. Should one of them have got more than
# 50% of the vote, report them as the winner and exit.
#
# Otherwise, keep the name of whoever got the smallest vote.
$lowestVote = [Int32]::MaxValue
$lowestName = ''
foreach ($candidate in $counts.Keys)
{
# least store
# since the total number of ballots is constant, then the lowest
# percentage of the vote is also the lowest absolute number of votes
if ([int32]$counts[$candidate] -lt $lowestVote)
{
$lowestVote = [int32]$counts[$candidate]
$lowestName = $candidate
}
# win check
$percentage = $counts[$candidate] / $ballots
if ($percentage -gt 0.5)
{
Write-Host ($candidate + " is the winner with " + ("{0:f2}" -f ($percentage * 100)) + " % of the vote.")
exit
}
}
# If we get here, then no-one got over 50% of the vote; now we need
# to iterate the ballots and remove the candidate who received the lowest
# percentage, $lowestName.
foreach ($ballot in $votes)
{
$ballot.Remove($lowestName)
}
}
Official method's a bit different as it marks the void ballots as "VOID" rather than removing them altogether, and joins and splits to substitute VOID in the ballot strings rather than removing them from arrays. I like mine better, but then, I would...
And my Perl solution:
#!C:\Perl\bin\perl.exe
use strict ;
# Advanced Event 3: Instant (Runoff) Winner
# vote file
my $voteFile = 'C:/Scripts/votes.txt' ;
# Going to do this one like the PowerShell one, I think, turning each person's
# ballot into an array and then placing references to those arrays into a
# second array, @votes.
#
# Since we count first choices (modulo removed votes on subsequent iterations),
# we just walk down $votes tallying up the [0] element of each sub-array.
#
# When it's time to remove a candidate, we iterate the $votes array and each
# sub-array inside it finding and removing that candidate. It should be
# impossible to remove all the items from any given ballot before we get
# a result, so we don't need to check for that when we're counting.
my @votes ;
# first, we suck in the votes
{
open (VOTES, $voteFile) ;
while()
{
my @ballot = split (/,/) ;
push (@votes, \@ballot) ;
}
close VOTES ;
}
my $ballots = scalar @votes ;
# counting routine
while (1)
{
# iterate each ballot's first choice and count them
my %counts ;
foreach my $ballot (@votes)
{
$counts{@$ballot[0]}++ ;
}
# Iterate the counts. Should one of them have got more
# than 50% of the vote, report them as the winner and exit.
my $lowestVote = $ballots ; # no-one can have more than this
my $lowestName = '' ;
foreach my $candidate (keys %counts)
{
# least store
# since the total number of ballots is constant, then the lowest
# percentage of the vote is also the lowest absolute number of votes
if ($counts{$candidate} < $lowestVote)
{
$lowestVote = $counts{$candidate} ;
$lowestName = $candidate ;
}
# win check
my $percentage = $counts{$candidate} / $ballots ;
if ($percentage > 0.5)
{
my $printpc = sprintf ("%.2f", ($percentage * 100)) ;
print "$candidate is the winner with $printpc% of the vote.\n" ;
exit ;
}
}
# if we get here, then no-one got over 50% of the vote; now we need
# to iterate the ballots and remove the candidate who received the
# lowest percentage
foreach my $ballot (@votes)
{
# find the index of the candidate in the ballot
# won't I feel dumb if Perl hashes are ordered?
# but I don't believe they are
# ...
# No, they aren't. Good.
for (my $i = 0; $i < scalar (@$ballot); $i++)
{
if (@$ballot[$i] eq $lowestName)
{
splice (@$ballot, $i, 1) ;
last ;
}
}
}
}
Which is, as I'm sure you noticed, a straight rip of my PowerShell solution.
So, this is a book I got quite some time ago from the LibraryThing early reviewers program. Unfortunately, in this case, it's quite a late review - my initial thumb-through of the book was a little off-putting, so others ended up taking precedence for quite a while.
As some of you may know, I occasionally dabble in the waters of occult, usually hermetic, philosophy, alchemy, and so forth, sometimes purely because it's quite an interesting intellectual construct to play with, and at other times because even shorn of the metaphysics which I'm disinclined to believe in, its practitioners often come up with worldviews, methods of classification, etc. that are useful "mind tools" in their own right.
Unfortunately, while I was hoping for a fairly sophisticated treatment of the analogy between classical Western (and maybe even Eastern) alchemy and spirituality, On Becoming an Alchemist is regrettably too "light" and "new age"-y to fit that role, and the conversational "pop-culture" style - Harry Potter references included - didn't fit the topic very well, in my opinion; nor did the various attempts to link it to scientific theories bent to suit. I fear I like my hermeticism a bit more, well, hermetic, if you will.
While I'm sure it probably has appeal for people closer to its target audiece, if there is such a book out there to satisfy me, this book is not it, alas.
As usual, scenario here - in this case, a simple one, counting the number of lines in the characters file itself - and official solution here for PowerShell and here for Perl. My PowerShell solution follows:
# Beginner Event 4: Count Yourself in
# first, find myself
$myPath = $MyInvocation.MyCommand.Definition
# read me in, accumulate the lines
$content = Get-Content $myPath
$cc = 0
foreach ($line in $content)
{
$cc += $line.Length
}
Write-Host "I am" $cc "characters long."
Short and sweet enough to really point up the differences between mine and the official ones, eh? I seem to like line spacing, initializing my variables even when not technically necessary, and verbiage in comments and messages.
My Perl script:
#!C:\Perl\bin\perl.exe use strict ; # Beginner Event 4: Count Yourself In # don't need to find myself; I am $0. open (ME, $0) ; my $cc = 0 ; while () { chomp ; $cc += length ($_) ; } print "I am $cc characters long."
For once I seem to be coming up shorter than the official answer. If you took out the as-above, anyway.
As usual, scenario here, and official solution here for PowerShell and here for Perl. My PowerShell solution follows:
# Beginner Event 3: Let's Get Together # Source and Target Files $sourceFiles = 'C:\Scripts\*.txt' $targetFile = 'C:\Temp\newfile.txt' # one-liner! Get-ChildItem $sourceFiles | Get-Content -totalCount 1 | Set-Content $targetFile
One-liners all around, even if mine is slightly more verbose.
My Perl script:
#!C:\Perl\bin\perl.exe
use strict ;
# Beginner's Event 3: Let's Get Together
# Source and Target Files
my $sourceDirectory = 'C:/Scripts';
my $targetFile = 'C:/Temp/Newfile.txt';
opendir (DIR, $sourceDirectory) ;
my @txtfiles = grep { /\.txt$/ && -f "$sourceDirectory/$_" } readdir(DIR);
closedir DIR ;
open (OUTPUT, "> " . $targetFile) ;
foreach my $file (@txtfiles)
{
open (INPUT, $sourceDirectory . "/" . $file) ;
# reading just one line makes it the first; temp variable
# enforces scalar context
my $temp = <INPUT> ;
print OUTPUT $temp ;
close INPUT;
}
close OUTPUT ;
Pretty much the same as theirs. And again, while it doesn't matter on scripts this trivial, I believe mine to be slightly more disk-efficient. Mine only reads one line from each input file, while theirs reads them all in and throws the rest away. Of course, theirs is also more readable without needing a comment to explain just what the hell's going on in that bit...
Challenge - turn a string of numbers into text - is here; official solution is here. Trivial, but trivial:
# Sudden Death Challenge, Day 4
$numbersFile = 'C:\Working\ScriptingGames\Numbers.txt'
# read the numbers into a string
$numbers = Get-Content $numbersFile -totalCount 1
# count along $numbers by pairs of characters; of course,
# it must contain an even number of characters
if (($numbers.Length % 2) -ne 0)
{
Throw "Error in file; not an even number of digits."
}
for ($i = 0; $i -lt $numbers.Length; $i += 2)
{
# assemble
$num = [string]$numbers[$i] + [string]$numbers[$i + 1]
Write-Host ([char][int]$num) -nonewline
}
Write-Host ""
Dear Writers of and Commenters at Left-Leaning Blogs1:
Can y'all ease back on the Obama assassination fantasies, already? Yes, we get that you're deeply worried that someone might take hostile partisanry to the next level, and some of us are even willing to admit that it might not even just be projection, but the repetition and loving detail and craftsmanship some of you are putting into your scenarios are (a) verging on necrophilia, and (b) creeping us right the fuck out.
Thank you for your consideration,
Us
Footnotes:
[1] Or at least the ones I read for balance, since I don't really care what you do on your own time.
Again as usual, scenario - scoring a figure-skating contest - here, PowerShell solution here, Perl solution here.
My PowerShell solution:
# Advanced Division, Event 2: Skating on Thin Ice
# skater list file
$skaterListFile = 'C:\Working\ScriptingGames\Skaters.txt'
# Read in the lines from the skater list file, splitting each line on the
# commas, and then place it into a hashtable with the first item (their
# name) as the key, and the array of remaining items (the scores) as the
# value.
$scores = New-Object System.Collections.Hashtable
foreach ($line in (get-content $skaterListFile))
{
$name, $score = $line.Split(',')
$scores.Add($name, $score)
}
# Iterate through the stored skaters, computing each one's score according
# to the rules. Then, having done this, store the result in a
# SortedList, to get IndexOfValue.
$finalscore = New-Object System.Collections.SortedList
foreach ($k in $scores.Keys)
{
$iscores = $scores[$k]
# Since we know there are seven scores each...
# (and I'm a paranoid scripter, so I check that)
if ($iscores.Length -ne 7)
{
Throw "Wrong number of scores for skater " + $k
}
# ...we can simply sort the individual score array and average the
# middle five scores to drop the highest and lowest.
[System.Array]::Sort($iscores)
$average = ( `
[System.Int32]::Parse($iscores[1]) + `
[System.Int32]::Parse($iscores[2]) + `
[System.Int32]::Parse($iscores[3]) + `
[System.Int32]::Parse($iscores[4]) + `
[System.Int32]::Parse($iscores[5])) / 5
$finalscore.Add($k, [double]$average)
}
# To get the top three scores, we simply pull out the keys array of the
# SortedList of averages, sort it, and display the last three items in
# reverse order.
# this next funny-looking line makes an array of the right length for CopyTo
$topscores = ,0 * ($finalscore.Values.Count)
$finalscore.Values.CopyTo($topscores, 0)
[System.Array]::Sort($topscores)
$goldscore = $topscores[$topscores.Length - 1]
$silverscore = $topscores[$topscores.Length - 2]
$bronzescore = $topscores[$topscores.Length - 3]
$goldwinner = $finalscore.Keys[$finalscore.IndexOfValue($goldscore)]
$silverwinner = $finalscore.Keys[$finalscore.IndexOfValue($silverscore)]
$bronzewinner = $finalscore.Keys[$finalscore.IndexOfValue($bronzescore)]
Write-Host "Gold medal : $goldwinner, $goldscore"
Write-Host "Silver medal : $silverwinner, $silverscore"
Write-Host "Bronze medal : $bronzewinner, $bronzescore"
Mine obviously different from theirs, since for sundry reasons - trying the VBScript technique of storing stuff in a disconnected ADO RecordSet - they're doing it a non-obvious way. I think this works passing well for pure PowerShell, though.
And my Perl solution, which is essentially the same:
#!C:\Perl\bin\perl.exe
use strict ;
# Advanced Division, Event 2: Skating on Thin Ice
# skater list file
my $skaterListFile = 'C:\Working\ScriptingGames\Skaters.txt' ;
# Read in the lines from the skater list file, splitting each line on the
# commas, and then place it into a hashtable with the first item (their
# name) as the key, and the array of remaining items (the scores) as the
# value.
my %scores ;
{
open (SKATERS, $skaterListFile) ;
foreach my $line ()
{
chop $line ;
my @spline = split (/,/, $line) ;
# use a temp variable or for some reason the slice doesn't
# get stored in the hash properly.
# got to find better documentation on doing complex data
# structures in this language.
my @temp = @spline[1..7] ;
$scores{@spline[0]} = \@temp ;
}
}
my %finalScores ;
# Iterate through the stored skaters, computing each one's score according
# to the rules. Then, having done this, store the result in a new hash.
{
foreach my $skater (keys (%scores))
{
# Since we know there are seven scores each...
# (and I'm a paranoid scripter, so I check that)
@{$scores{$skater}} == 7 or die "Wrong number of scores for skater $skater." ;
# ... we can simply sort the individual score array and average the
# middle five scores to drop the highest and lowest.
# BUT WAIT! perl does a stringwise sort by default, not an intwise
# sort, so we end up with this line in the middle:
# 53 63 65 8 81 89 96
# so we need to add a stock block to make it sort intwise
my @iscore = sort {$a <=> $b} ( @{$scores{$skater}} ) ;
my $fsc = 0;
foreach my $sc (@iscore[1 .. 5])
{
$fsc += $sc
}
$fsc = $fsc / 5 ;
$finalScores{$skater} = $fsc ;
}
# Get an array of the keys to the finalscores hash, sorted by the values
my @scoring = sort { $finalScores{$b} <=> $finalScores{$a} } keys %finalScores;
# ...and then display the final scores from it.
print "Gold medal : @scoring[0], $finalScores{@scoring[0]}\n" ;
print "Silver medal : @scoring[1], $finalScores{@scoring[1]}\n" ;
print "Bronze medal : @scoring[2], $finalScores{@scoring[2]}\n" ;
}
Much as above, really; similar to their script, but wordier.
As usual, scenario here, and official solution here for PowerShell and here for Perl. My PowerShell solution follows:
# Advanced Division, Event 1: Could I Get Your Phone Number?
# wordlist file
$wordlistFile = 'C:\Working\ScriptingGames\wordlist.txt'
# keypad letters
$keypad = (
('*'),
('*'),
('A', 'B', 'C'),
('D', 'E', 'F'),
('G', 'H', 'I'),
('J', 'K', 'L'),
('M', 'N', 'O'),
('P', 'R', 'S'),
('T', 'U', 'V'),
('W', 'X', 'Y')
)
# Prompt the user for a telephone number.
$phone = Read-Host -prompt "Enter a seven-digit telephone number (no hyphen)"
# Validate the entered telephone number.
if (($phone.Length -ne 7) -or ($phone -notmatch "[0-9]{7}"))
{
Throw "Invalid telephone number format."
}
# Using the telephone number, generate all possible selections of
# the keypad letters...
$letters = New-Object System.Collections.ArrayList
foreach ($char in $phone.ToCharArray())
{
# throw an error if one of the digits is 0 or 1, since they
# have no corresponding letters
if ($char -lt '2')
{
Throw "Cannot translate digits 0 or 1 to letters."
}
$null = $letters.Add($keypad[[System.Int32]::Parse($char)])
}
# ...and then string them together into all possible words.
# (a series of nested loops)
$possibles = New-Object System.Collections.ArrayList
foreach ($l0 in $letters[0])
{
foreach ($l1 in $letters[1])
{
foreach ($l2 in $letters[2])
{
foreach ($l3 in $letters[3])
{
foreach ($l4 in $letters[4])
{
foreach ($l5 in $letters[5])
{
foreach ($l6 in $letters[6])
{
$null = $possibles.Add("$l0$l1$l2$l3$l4$l5$l6")
}
}
}
}
}
}
}
# Pull in the word list into an appropriate collection.
# To save time on lookup, we will only stash words exactly seven letters long.
$wordlist = New-Object System.Collections.ArrayList
Get-Content $wordlistFile | ForEach-Object {
if ($_.Length -eq 7)
{
$null = $wordlist.Add($_.ToUpper())
}
}
# Run through the generated combinations until one appears in the word
# list, and then print it.
foreach ($p in $possibles)
{
if ($wordlist.Contains($p))
{
Write-Host "Word matching supplied number:" $p
exit
}
}
Throw "No words match the supplied number."
Differences? Well, lots of trivial ones. They store the entire word list, not just the ones they need (7-characters) - twice over, actually, since those variables won't be GCed until the script terminates - and use a space trick to search it; and, granted, for a short script that's pretty trivial even for 320K of data. I still call it inelegant, though.
Also, their algorithm finds a randomly-ordered word that matches the telephone number, while mine always finds the first word. All the spec says on that is:
On top of that, keep in mind that: 1) although there might be multiple solutions for a given phone number your script should only display one solution; and 2) the script should only display a correct solution.
So you pays your money and you takes your choice, really.
My Perl script:
#!C:\Perl\bin\perl.exe
use strict ;
# Advanced Division, Event 1: Could I Get Your Phone Number?
# wordlist file
my $wordListFile = 'C:/Working/ScriptingGames/wordlist.txt' ;
# keypad letters
my @keypad = (
[ '*' ],
[ '*' ],
[ 'A', 'B', 'C' ],
[ 'D', 'E', 'F' ],
[ 'G', 'H', 'I' ],
[ 'J', 'K', 'L' ],
[ 'M', 'N', 'O' ],
[ 'P', 'R', 'S' ],
[ 'T', 'U', 'V' ],
[ 'W', 'X', 'Y' ]) ;
# Prompt the user for a telephone number.
print "Enter a seven-digit telephone number (no hyphen): ";
my $phone = ;
chop $phone;
# Validate the entered telephone number.
if ((length($phone) != 7) or !($phone =~ /[0-9]{7}/ ))
{
die "Invalid telephone number format."
}
# Using the telephone number, generate all possible selections of
# the keypad letters...
my @letters ;
{
my $char ;
foreach $char (split ('', $phone))
{
# throw an error if one of the digits is 0 or 1, since they
# have no corresponding letters
($char < '2') and die "Cannot translate digits 0 or 1 to letters." ;
push (@letters, @keypad[$char]) ;
}
}
# ...and then string them together into all possible worlds.
# ( a series of nested loops)
my @possibles ;
{
for (my $a = 0; $a < 3; $a++)
{
my $ac = @letters[0]->[$a] ;
for (my $b = 0; $b < 3; $b++)
{
my $bc = @letters[1]->[$b] ;
for (my $c = 0; $c < 3; $c++)
{
my $cc = @letters[2]->[$c] ;
for (my $d = 0; $d < 3; $d++)
{
my $dc = @letters[3]->[$d] ;
for (my $e = 0; $e < 3; $e++)
{
my $ec = @letters[4]->[$e] ;
for (my $f = 0; $f < 3; $f++)
{
my $fc = @letters[5]->[$f] ;
for (my $g = 0; $g < 3; $g++)
{
my $gc = @letters[6]->[$g] ;
push (@possibles,
$ac . $bc . $cc . $dc . $ec . $fc . $gc );
}
}
}
}
}
}
}
}
# Pull in the word list into an appropriate collection.
# To save time on lookup, we will only stash words exactly seven letters long.
my %wordList ;
{
open (WORDS, $wordListFile) ;
foreach my $word ()
{
chop $word ;
if (length ($word) == 7)
{
$wordList{uc($word)} = 1 ;
}
}
}
# Run through the generated combinations until one appears in the word
# list, and then print it.
my $p ;
foreach $p (@possibles)
{
if (exists ($wordList{$p}))
{
print "Word matching supplied number: $p" ;
exit ;
}
}
die "No words match the supplied number." ;
...functions in more or less the exact same way that my PowerShell one does, and stands in pretty much the same relation to their Perl one, so not much to say here.
Except that if I hadn't written this one second, I might have come up with something more like theirs, to avoid being sucked into the crawling horror of syntax that is using array references, etc. in Perl to emulate proper multidimensional arrays. Ugh.
Challenge - calculating President name facts - is here; Microsoft solution is here.
Different technique, especially for working out the letters-not-used-in-initials, but the effect is much the same:
# Sudden Death Challenge, Day 3
# read in the president data
$presidents = Get-Content 'C:\Working\ScriptingGames\Presidents.txt'
$longestNameLen = 0
$longestName = ''
$initials = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
$vowels = 0
# function to remove characters from the initials list
function removeFromInitials([char]$init)
{
$index = $initials.IndexOf($init)
# if not already removed...
if ($index -ne -1)
{
# ...remove it
$initials = $initials.Remove($index, 1)
}
$initials
}
# iterate through and compute the various things
foreach ($prez in $presidents)
{
$lastname, $firstname = $prez.Split(',')
$firstname = $firstname.TrimStart(' ')
# Find the longest firstname.
if ($firstname.Length -gt $longestNameLen)
{
$longestNameLen = $firstname.Length
$longestName = $firstname + " " + $lastname
}
# Get initials, and remove them from our arraylist.
$initials = removeFromInitials($firstname[0])
$initials = removeFromInitials($lastname[0])
# Count the vowels.
for ($i = 0; $i -lt $prez.Length; $i++)
{
if ($prez[$i] -match "[aeiou]")
{
$vowels++
}
}
}
Write-Host "Longest first name:" $longestName
Write-Host "Initials not used :" $initials
Write-Host "Total vowels used :" $vowels
Okay, so clearly there is some difference between the sorts of words that I think constitute common vocabulary such as can reasonably be expected to be found in the reasonably intelligent average person's vocabulary, and the ones that actually are. (Well, I know that, but evidently it's bigger than I thought, which - I am informed - is likely the result of my conscious intellectual elitism not being nearly as big as it ought to be; evidently I'm overestimating people again.)
So, I thought I'd try a little poll. Here are twenty words randomly selected from those I've come across recently which I consider well inside my "common, general knowledge" vocabulary. Perspectives? Any subgroups seem particularly obscure? Anything?
- Arbitrage
- Cation
- Conjugation
- Enfilade
- Enthymeme
- Epistemology
- Equity
- Estoppel
- Hexameter
- Increment
- Lepton
- Mohorovicic
- Monopsony
- Mutex
- Quasar
- Ribosome
- Syllogism
- Synecdoche
- Valence
- Viridian
I don't care who the hell you think you are, the law of supply and demand will not be abolished for your convenience.
And I don't care who the hell you plan to vote for, but they won't be able to do it for you.
Nor is the world and everyone in it created for your and/or their utilization.
It is not reasonable to expect people to conduct their affairs on the assumption that everyone with whom they interact is a faithless, feckless weasel, to whom they assume inescapable obligations.
Unfortunately, the world is full of the former, and often enforces the latter.
People who assume any and all the above are true and/or just and/or right are just some of the many reasons why occasionally I'm tempted - like right now - to believe the Involuntary Human Extinction Movement has the right idea.
That is all.
And now, the solution to Beginner Division Event 2: True Type, and I think from now on I'm just going to post the PowerShell and the Perl versions together, on the grounds that those of you who don't care would rather skip three posts a day between now and March 3rd instead of five posts a day. You can read the scenario here, and the official solution is here (PowerShell) and here (Perl). My PowerShell solution was:
# Beginner Division, Event 2: True Type
# Registry path containing the font names
# All fonts are stored as registry values under this key.
# This PowerShell drive is be in the default config.
$fontPath = 'HKLM:\Software\Microsoft\Windows NT\CurrentVersion\Fonts'
# Thus, enumerate the fonts thus:
$fonts = ( Get-Item $fontPath ).Property
# Might as well have them in order...
[System.Array]::Sort($fonts)
$ttCount = 0
foreach ($font in $fonts)
{
# display and count truetype fonts (by name)
if ($font -match "\(TrueType\)$")
{
Write-Host $font
$ttCount++
}
}
# Display totals
Write-Host "`nTrueType: " $ttCount
Write-Host "Total: " $fonts.Count
Pretty similar to the official solution, except that I use a regex to pick out the TrueType fonts rather than the .Contains method - trading off, I suppose, a small performance penalty against the possibility that there is a non-TrueType font on the system with "TrueType" in its name.
And here's my Perl solution:
#!C:/Perl/bin/perl.exe
use strict ;
# Beginner Division, Event 2: True Type
# Registry path containing the font names
# All fonts are stored as registry values under this key.
my $fontPath = "Software\\Microsoft\\Windows NT\\CurrentVersion\\Fonts" ;
# The docs say this is the newer one to use, so:
# use Win32::TieRegistry ( Delimiter => '/' ) ;
# Okay, looks like that one can't be used because of what various sites
# appear to agree is a Perl bug of some kind. Unfortunately, they can't
# manage to agree on what the Perl bug actually is. Sigh.
#
# Let's try the old and clunky one.
my @fonts ;
use Win32::Registry ;
# Open the registry key.
{
my $fontKey ;
$::HKEY_LOCAL_MACHINE->Open($fontPath, $fontKey) or die "Can't open fonts: $^E" ;
my %values ;
$fontKey->GetValues(\%values) ;
# Extract the font names from what we end up getting.
my $alt = 1 ;
my $val ;
foreach $val (%values)
{
if ($alt)
{
push (@fonts, $val) ;
}
$alt = !$alt ;
}
}
# Ok, at this point we should have a list of all the font names,
# despite this ugly hackery.
# might as well have them sorted
@fonts = sort @fonts ;
# first, print and count the TrueType fonts (by name)
my $ttCount = 0 ;
my $font ;
foreach $font (@fonts)
{
if ($font =~ /\(TrueType\)$/)
{
print "$font\n" ;
$ttCount++ ;
}
}
my $totalCount = scalar(@fonts) ;
print "\nTrueType: $ttCount\n" ;
print "Total : $totalCount\n" ;
This one's also pretty similar to the official solution, except both it (and the provided tip, which I didn't look at) recommend using Win32::TieRegistry. Damned if I could get that to work. Script kept crashing with "Can't use an undefined value as an ARRAY reference at C:/Perl/lib/Win32/TieRegistry.pm line 684." - the bug that I refer to above which has me using the old'n'busted registry access method.
Challenge is here; Microsoft solution is here. Interesting that though they let you use any language for these, they're only posting the solution in VBScript. What, no PowerShell yet?
Anyway, the solution's essentially the same in principle:
# Sudden Death Challenge, Day 2
$corruptFile = 'C:\Scripts\vertical.txt'
$corrupt = get-content $corruptFile
$clean = New-Object System.Text.StringBuilder 32
for ($i = 0; $i -lt 4; $i ++)
{
foreach ($line in $corrupt)
{
$null = $clean.Append($line[$i])
}
}
Write-Host $clean.ToString()
And now, as a palate-cleanser for tired brains during this, our busy period, I read the first book of a YA series that I, for some reason, never actually read when I was one.
Conclusion: I enjoyed it as a short and pleasing adventure yarn, lightly spiced by the collision of the "supernatural" world with the "real" one. I also enjoyed that it expected its readers - of the younger age - to already know enough about certain things that they could make some connections for themselves.
Beyond that, I don't think I want to come to conclusions quite yet. I think it would be fairly weak as a standalone book (while it's good as far as it goes, were it to be a standalone, it would need to go further), but given that it's the start of the whole The Dark Is Rising series, I shall reserve judgment until further in.
Please be advised, gentle LiveJournal readers, that the Scripting Games run for pretty much the rest of this month, and I plan to be posting these solutions until they're over. However, I am aware that being wide <pre>-formatted text, that in conjunction with unhelpful styles, they may make your friends page "break", if "break" is the appropriate term for acquiring a horizontal scrollbar.
Please further be advised that whether I put them below the fold here at source or not, it makes no nevermind to LiveJournal, and that really, there's not much I can do here at source beyond bizarrely inconvenient workarounds I'm simply not inclined to do just because LJ's syndication code sucks.
I therefore suggest that if this is going to cause you angst and/or inconvenience, you place me in a "Posts Annoyingly Wide Things" friends-group until March 4th, or so, so you won't have to deal with it on your main friends page. Instructions on how to do so are here.
And here's the same thing in Perl. Their Perl solution here.
Not many comments on this one, seeing as I'm just learning the language. Feel free to criticize the crap out of my Perl style, though, for that same reason.
#!C:\Perl\bin\perl.exe
# Beginner Division, Event 1: Pairing Off
use strict ;
# We're going to define cards here as Perl objects. This is almost certainly
# an excessively baroque way of doing this, but what the hell - it's all part
# of learning the language as we go, right?
# As before:
#
# For the purposes of this encoding, the suits are as follows:
#
# Hearts = 0
# Spades = 1
# Clubs = 2
# Diamonds = 3
#
# Card ranks:
#
# Ace = 1, Jack = 11, Queen = 12, King = 13
{ package Card ;
# construct a card
sub new {
my $class = shift ;
my $suit = shift ;
my $rank = shift ;
my $self = { Suit => $suit, Rank => $rank } ;
bless $self, $class ;
}
# subroutine to display the card value
sub display {
print "$_[0]->{Suit} ; $_[0]->{Rank}" ;
}
# getter methods
sub suit {
$_[0]->{Suit}
}
sub rank {
$_[0]->{Rank}
}
# subroutine to compute crude unique card value
sub get_ucv {
"$_[0]->{Suit}:$_[0]->{Rank}"
}
}
# The cards we are using are given in the problem definition:
#
# Seven of spades, Five of hearts, Seven of diamonds, Seven of clubs
# King of clubs
my @cards = (Card->new(1, 7),
Card->new(0, 5),
Card->new(3, 7),
Card->new(2, 7),
Card->new(2, 13)) ;
# Technically, we needn't have bothered storing the suits; we can look for
# pairs using nothing but the card rank. On the other hand, let's just
# do a little extra work here and validate the card array we've got, to
# be sure.
{
my $current ;
my %seen ;
foreach $current (@cards)
{
# If the card has already been added to the associative array, the
# '1' returned causes the script to die.
die "Duplicate card" if $seen{$current->get_ucv}++
}
}
# OK, enough of this frivolity. Time to do the actual challenge, and
# compare the card pairs.
# Here's what we're doing. Take each card one at a time, and then go
# through the rest of the deck and count any pairs containing that card.
# Then throw that card away, since all possible pairs have been counted,
# and repeat using the remaining deck.
#
# Actually works on lists of arbitrary length, which exceeds the spec,
# but who's counting?
my $pairCount = 0 ;
while (scalar (@cards) > 1)
{
my $first = shift(@cards) ;
my $current ;
foreach $current (@cards)
{
if ($current->rank == $first->rank)
{
$pairCount++
}
}
}
print "There are $pairCount pairs."
Well, the first events of the scripting games are over and the solutions are up on the Script Center now, so here begins my tradition of around this time of year of putting my solutions up contemporaneously with the official ones - mostly because they're often at significant variance from the Official Solutions, and it's always nice to see other ways to do things.
Anyway, this is the PowerShell solution to Beginner Division Event 1: Pairing Off. You can read the scenario here, and the official solution is here. Mine was:
# Beginner Division, Event 1: Pairing Off # A card is defined as a pair of values, the first representing # the suit, the second representing the value. # # For the purposes of this encoding, the suits are as follows: # # Hearts = 0 # Spades = 1 # Clubs = 2 # Diamonds = 3 # # Card ranks: # # Ace = 1, Jack = 11, Queen = 12, King = 13 # The cards we are using are given in the problem definition: # # Seven of spades, Five of hearts, Seven of diamonds, Seven of # clubs, King of clubs $cards = ((1, 7), (0, 5), (3, 7), (2, 7), (2, 13)) # Technically, we needn't have bothered storing the suits; we # can look for pairs using nothing but the card rank. On the # other hand, let's just do a little extra work here and validate the # card array we've got, to be sure. $valid = new-object