This continues my series of solution posts for the 2008 Scripting Games with my solution for the Beginner’s Event 7 and
Beginner’s Event 8. I’m wrapping them together because beginner’s event 7 was just a debugging exercise so I just pasted the solution at the bottom1.
Event 8 was the age old “Higher or Lower” guessing game: the script picks a random number between 1 and 50 and then lets the user guess at it. After each guess, you tell them whether their guess was low or high, and when they guess it you tell them they got it, and how many guesses it took.
It’s a pretty simple script, and the only part that I thought was cool was the way I used the while loop to increment the counter (it’s important to PRE-increment it, so it’s not equal to 0 the first time it’s tested). I used the switch statement so that I wouldn’t need to store the user’s guess. Nothing to be proud of, nothing to really point out. If you didn’t know how to get a random number in .Net, the Next method of the Random object takes a minimum (inclusive) and maximum (exclusive).
1 The only interesting thing in DebugMe.ps1 was that there’s a possible extra bug because the script uses the variable $x without initializing it or taking it as a parameter — $x is a really popular variable ;-P and not initializing it could result in really strange results from this script. I fixed it (within the scope of the rules: only modify existing lines) by explicitly stating the script scope:
This continues my series of solution posts for the 2008 Scripting Games with my solution for the Beginner’s Event 6. This was actually the script that got me started solving the beginner scripts — someone asked about it in the first couple days of the scripting games, and it got me interested. 
The challenge was to take a series of coffee “orders” from a text file, and turn them into a single order. In the text file they’re written in the format:
So each office number has an order consisting of some number of Espressos, Lattes, and Cappuccinos.
MoW and the Scripting Guys seemed to have the same general idea here, to use a hash array. They each did a little bit more work than was strictly necessary, so here’s how I would have done that.
The key difference in my solution is that I didn’t initialize the $orders hashtable (because it doesn’t need to be initialized) and I filtered out the “Office” lines as early as possible to avoid processing them through the rest of the code — when you’re writing PowerShell pipeline scripts, always remember that you want to filter as early as possible.
However, I have to admit, that’s not how I solved it. Instead, I used Select-Object to create custom objects, grouped them, and then measured the sum of the “Count” property of the orders in each group:
You’ll notice that this isn’t as effective as the other solution: it has to do the split() twice, and basically groups everything and then un-groups it to get the count… but it’s the way I think: one command-line — a big long pipeline — instead of a foreach loop with multiple lines in it. By now you won’t be surprised to know that I compared the speeds of these, and the first approach runs in just about half the time of the second one.
This continues my series of solution posts for the 2008 Scripting Games with my solution for the Beginner Event 5 which was a simple test of your ability to accept command-line parameters and play with dates.
The only real trick was that you were required to calculate the number of months and days two different ways: total days and calendar months, and then complete months with days remainder. There are lots of ways to do this, but one of the simplest things is to use the VB.net DateAndTime function DateDiff:
You see, there’s nothing really worth talking about in that, other than the fact that it’s using that DateDiff, and the way I used join on the $args array to let you pass dates like “January 5, 2008” without the quotes.
This continues my series of solution posts for the 2008 Scripting Games with my solution for the Advanced Event 6. This was an interesting challenge because it’s a basic algorithms test … Mow, the scripting guy came up with some interesting brute-force solutions … but nearly everyone else just implemented the Sieve of Eratosthenes, since it’s the easiest of the prime sieves to understand.
I was slightly annoyed to be porting this simple algorithm to PowerShell, but I didn’t have the time to use an improved sieve, so I just ported it as directly as I could, without optimizing it for the PowerShell syntax, or removing any steps:
Mow asked about performance (because he wrote a brute -force solution, since the challenge was only to find up to 200) ... and I was reluctant to even mention it, because my script is such a direct port of the generalized algorithm that it’s guaranteed not to perform well, but I thought I’d post these numbers because they show how good Chris’s solution is. Remember these are numbers from my deliberately underpowered machine at work — don’t compare them to numbers from other computers — if you want to know how these scripts compare to yours, copy the code and run them all on your PC.
Oh, and by the way, that output is from a Get-PerformanceHistory script…
The guys in #PowerShell on IRC were making fun, and claiming that my problem was the initialization of the bool array (it’s not), so I had to explain the performance difference … and it’s really quite simple. Chris used $p = $n*$n to calculate the square of the iterator, which is a lot faster than $p = [Math]::Pow($n,2) which is what I used originally. I changed the script above to use $n*$n because that’s clearly a better choice for calculating squares … I wouldn’t want people using [Math]::Pow after seeing that difference. Here’s the new speed report (and yes, I made a new version of Get-PerformanceHistory which calculates averages if your command starts with a range like 1..10):
In the interests of full disclosure … (and because I’m and algorithms optimization geek, in case you haven’t noticed) that loop on the end of my script that outputs the values … and the fact that I therefore break out of the calculation loop when I hit the square root of the $max value … makes my script slightly faster for huge values of max (as above) , and somewhat slower for small values:
This continues my series of solution posts for the 2008 Scripting Games with my solution for the Advanced Event 5 which was a joke of a challenge — basically you have to run an arbitrary set of password “strength” rules over a string. The rules are not very complete, and result in such vagaries as “PASSword” getting a worse score than “Password” ... and my randomly generated “KiVExXwMxocScnIjUinCTKTA” getting a WORSE score than “PassWord1” ...
Anyway. Here’s the script, just for the sake of completeness. Please don’t use it for anything you care about
. (more…)
This continues my series of solution posts for the 2008 Scripting Games with my solution for the Advanced Event 4 which was about drawing a “graphical” calendar in the console. Basically, this is a simple task, and the only complication was the requirement to allow passing in the month.
There are solutions from the Scripting Guys and Ed Wilson already available, and both of them are pretty good solutions, so I would have left this post in the draft bin if it wasn’t for the fact that I already showed off screenshots of my final script (which is a bit over the top). Because I wrote such a complicated script in the end, I wanted to show you how it started before I show you how it ended up:
See that? Not really very complicated, right? It’s just about ten lines of code, and three of them are unnecessary variable assignments which just help make the script more readable (actually, there’s really only 2 necessary lines1). There’s a couple of tricks here though: (more…)
This continues my series of solution posts for the 2008 Scripting Games with my solution for the Advanced Event 3 which was about counting votes in an instant run-off election. The idea was to take a CSV file with lines representing “ballots” such that each ballot should be a list of four candidates in order of preference. The election results are calculated by counting up the votes, and if no candidate receives a majority of the votes, the candidate with the least votes is eliminated from all ballots, and they are recounted. This continues until one candidate is the highest rated candidate on the majority of the ballots.
This time, the Scripting Guys’ solution is not only inefficient, it actually promotes bad coding practices! They actually use a do{ ... } while() loop (as do I) but instead of testing for an exit case in it, they test for $x -ne 1. But $x is a variable they don’t ever set, so in other words, they took the simplest loop ever and made it hard to understand by obscuring what the exit case is. Worse than that, although they think they’re basically testing do{ ... }while($true), they never set $x, so if $x is set to 1 in your environment before you run their script, their script will run with no output.
The rest of their solution is no less confusing, involving the use of a hashtable which they treat as if it were some sort of special dictionary, calling get_item and set_item instead of using indexing:
Honestly, even if you think of this as a VBScript converted to PowerShell, the solution is bad. It’s even determining the number of votes (which they could get at any time from $arrContents.Count) using a manually incremented counter … which they reset and recalculate on each recount (as though it might change).
The contributed solution was written by Don Jones for this event … and is basically what I expected the Scripting Guys solution to be: a good VB-script style solution. Except, the script they published for him on their website doesn’t actually do anything, because it never calls his “Tally” function… I’m probably being overly critical, so I should just stop now, show you my solution, and leave the comment form open so people can flame me in turn. ![[argue]](http://HuddledMasses.org/wordpress/wp-content/plugins/smilingmasses/argue.gif)
Very simply: on the first line we read in the text file. Then we enter a loop where we calculate the number of votes for each candidate in this round, and then remove the lowest candidate from the ballots. When we exit, we print out the winner and the percentage of the total using string -f formatting.
I’ll give you a little more detail on the two lines inside the loop. The first line just takes the votes, and splits each line on the commas, and selects the first vote. These votes are grouped, sorted by count, and $votes is assigned the custom objects with just the candidate’s name and number of votes. The second line splits on the commas again, filters out items which match the candidate with the lowest vote count, and then joins them back together. Now, obviously that’s not the most efficient way of doing things, but it’s very simple. Of course, with the sample set of votes provided as part of the scenario, the voting goes all the way to the last round, so the string split and rejoin each time takes it’s toll. You could cut the runtime of the script to one fourth by simply moving that split out of the loop:
Incidentally, I have to point out again how cool it is that you can do: $array = $array -ne "value" to remove items that match “value” from an array. 
This continues my series of solution posts for the 2008 Scripting Games with my solution for the Advanced Event 2 which was about scoring a skating contest. The idea was to take a CSV file with skater names and scores (7 per skater), and calculate the average (leaving off the highest and lowest) score for each and award medals to the top three scorers. The Scripting Guys solution for this problem was kind-of interesting, because they used a DataSet to do it, but it’s truly an abomination which uses COM interop and a bunch of mysterious magic numbers like $adFldIsNullable = 32 and $adDouble = 5 … whatever those are.
Their script won’t even run on my computer. they didn’t specify a version on their COM object, and the ADOR.Recordset that I get on my Vista computer throws an error on line 7 of their script: Cannot find an overload for "Append" and the argument count: "4" and once I add a 5th argument to that, it throws dozens more like until I changed (among other things) their line $DataList.Fields.Item("Competitor") = $strName to $DataList.Fields.Item("Competitor").Value = $strName to get it to run. Anyway. For the record: don’t use COM stuff when you could use .Net stuff, because it adds measurably to the run time, and makes you do crazy things like defining a bunch of numeric constants. .
Anyway, before I show you my solution, let me show you how to load this data into a .Net DataTable. Even though it’s obviously not necessary for this script, but it’s an interesting PowerShell script, so here’s their solution rewritten with the System.Data.DataTable:
That’s much simpler and clearer than the original script (and even, I dare say, than the alternate solution from Ben Pearce) partly because it doesn’t need all the predeclared constants and the cursor stuff like Open() and MoveNext() ... but it’s nowhere near an ideal PowerShell solution. That would look something like this:
That’s basically a 2 line script which I reformatted to make readable. Let’s go through it slowly in case you need some help understanding it. Obviously the very first line is assigning the output of the command to the $skaters variable, and using Get-Content to read in the CSV file with the scores and pipe it to the next line. Remember, Get-Content reads in a single line at a time and passes that down the pipeline, so the next command in the pipeline will be dealing with a single line at a time.
The lines in the skaters.txt file consist of a skater’s name followed by 7 numbers (the scores that skater received). So on the next line we will create a custom PowerShell object for each skater … and use Select-Object to get the Name and Score.
We’re taking advantage of the Select-Object’s ability to use hash tables to define custom properties using script blocks. The full syntax for that is {Name="Property Name"; Expression={ ... }} where the Expression property can use the built in $_ variable which represents the pipeline object (in this case, the full line). As you can see, you can use just n and e instead of the full names for the hashtable. This part of the script might be better in a loop as above, so you could do the string split just once and assign it to a variable, but keeping it in the pipeline is ultimately smoother.
When we split the line on the commas, the first value (at index 0) is the skaters Name: $_.split(",")[0] and everything after that are the scores. Of course, we want to remove the highest and lowest values, so we use PowerShell’s array indexing to trim them off: $_.split(",")[1..7] will return all the scores, and we pipe that through sort, and then index just [1..5] of the sorted numbers. Then we pipe that through the Measure-Object cmdlet to calculate the Average. Then, we wrap the whole thing in parentheses so we can actually get the Average property of the GenericMeasureInfo object that Measure-Object outputs … which of course is a double number which is assigned to the Score property.
Then, we just pipe these custom objects into a sort by the score property, and select the highest three and output them (into that variable I mentioned earlier). It’s important to realize that even though I’ve wrapped the lines in the script on the pipe characters | so they would fit in the web page, PowerShell thinks of them as a single command line — which is why the output of the Select at the end ends up in the $skaters variable. 
But of course, the contest called for us to specify the medals as part of the output. That’s pretty simple, and we can use the string formatting built into PowerShell … here’s what it looked like originally (as a single line command, but wrapped for the sake of the web):
So. That’s pretty much it.
In my ongoing chronicle of solutions to the 2008 scripting games events … the task in beginner’s event 3 was to copy the first line of each text file in a folder into a new text file. This task is so trivial in PowerShell that my solution is the same as the Scripting Guy’s:
If you need that explained … well, you’re just going to have to read their explanation, because quite frankly, it’s too boring to get into.
For beginner’s event 4 the task was to read in the content of the script file itself and count the characters. This is actually just as easy as the last one, but somehow, the scripting guys’ solution went off into vbscript loop land again. They actually loop through each line and manually add up the line length. My script is 55 characters long
and looks like this:
Slick, right? Ok, I’ll explain that one. $MyInvocation is a built-in variable which contains everything there is to know about what’s running and how it was started. $MyInvocation.MyCommand contains the information about the command that’s running, it’s .Path if it’s a script, and so on. On the other hand, $MyInvocation.InvocationName contains the first thing on the command-line, and so even though the scripting guys used it, you should never assume InvocationName will contain the script path — it would fail if the script had been run by dot-sourcing, or by invoke-expression or by the invoke character & or … well, you get the idea, it would fail often.
Anyway, once you have the path of the script you get-content (gc is one of the built-in aliases for that) and pipe it into the Measure-Object cmdlet which has a -Characters parameter. As always, when passing parameters, you only need to pass enough to differentiate the parameter from others.
In normal use, that would be enough: the output of Measure-Object is simple and very clear. For the scripting games, we want to be a little more precises, so we pipe the output to the Format-Wide cmdlet using it’s built in alias “fw” — unlike Format-Wide and Format-List, Format-Wide won’t add headers, so it outputs just the number. However, we have to specify which property to show, but since there’s only a few, we can use a wildcard match to select the Characters and a single letter is enough to differentiate from the other properties: Lines, Words, and Property. And that’s it!
Right up front I should apologize to anyone who’s not a PowerShell user, because it’s just become clear to me that for the rest of the scripting games, I’m going to be posting my solutions to most, if not all, of the puzzles. I thought for sure that I wouldn’t have any reason to post solutions to the advanced scripting games puzzles, because in addition to the Scripting Guys solutions, we were going to have solutions from luminaries of the PowerShell scripting world. After looking at the first two of those solutions, however, one thing is perfectly clear: when under pressure to perform, or when attempting to turn a scripting problem into a scripting lesson … anyone can turn something simple into something really complicated.
So, I’ve decided to offer my solutions to these events. I’m not implying they will always (or ever) be the best possible solutions, but they’re alternatives you can learn from
.
Advanced Event 1 was a fun little puzzle: given a spell-checker-style word list, you should prompt the user for their phone number, and then find a word in the dictionary which could be used to represent that number according to the standard telephone dialpad’s letter arrangement. You can find the solution proposed by the scripting guys as well as Richard Siddaway on the scripting games site, but I know that other bloggers have posted their solutions as well — /\/\o\/\/ (the PowerShell Guy)‘s solution looks a bit like Richard Siddaway’s first one actually in that they both create lookup hashtables in an attempt to speed things up.
Incidentally, if I were grading a PowerShell class, and you handed in the Scripting Guy’s solution — you would get a D — it works, most of the time, but apart from being hideously inefficient and showing a lack of knowledge of the language, it’s non-deterministic and may not find the answer at all, even if there is one!
Compared to any of the solutions above, mine is dead simple, and the reason is this: when I think about finding a word that matches a certain pattern … I think regular expressions. It is, by far the fastest of any of these solutions, even with mow and Richard’s lookup tables … and I’ll let you judge it’s readability:
Incidentally, if you switch the last line to: select-string $pattern C:\Scripts\WordList.txt | % {$_.Line } it will output every possible match, instead of just the first one … and it works equally well for 10-digit phone numbers with area codes! In fact, if you take off the + "$" on the end of the pattern line, it will even find words that start with the digits in the phone number, which is usually good enough (in the USA, at least, you can dial extra digits after the phone number without any effect). Plus, that way you can look up what words might work for phone numbers starting with a specific exchange, etc.
To give you an idea … I tested with four phone numbers I generated from words starting with a,y,w, and r … and this script runs in about 1/3 of a second to find all four matching words. On the same box, using get-content and a pipeline involving -match as in Richard Siddaway’s solution takes about 27 and a half seconds, and mow’s lookup table solution takes about 18 seconds to build the table, and 2.43 seconds to do the lookups.
Incidentally, if that [string]::join call in the middle of my script makes you cringe, you could use this, instead: