Archive for the “Scripting Games” Category

During the Scripting Games I found myself creating a lot of custom objects with properties that I could use to sort , select, take averages of, and a number of other cool things. Getting results into a Powershell object can make life a lot easier for a number of reasons.

There was one little piece I was missing. Not only did I want to create a single object, quite often I would want to put all the objects I created into a collection of objects. Did you know you can add collections of like objects to each other ?

   1: PS 13 >  $a = get-process

   2: PS 14 >  $a.count

   3: 66

   4: PS 15 >  $b = get-process

   5: PS 16 >  $c = $a + $b

   6: PS 17 >  $c.count

   7: 131

   8: PS 18 >

The code above shows that I can add two collections of process objects together. Very cool.

So I tried doing this in the scripting games and came across a problem. For instance, in Event 3 we needed to tally up a bunch of votes. So what I really needed was to create a bunch of $vote objects and put them all together in a collection called $votes

Here’s what I came up with at first.

By the way, when I create PS Custom Objects I cheat and use the “” | Select-Object prop1, prop2 nomenclature. My easier than using new-object followed by a bunch of add-member commands.

   1: $votes = "" | Select-Object v1,v2,v3,v4

   2: foreach ($v in Get-Content votes.txt)

   3:     {    

   4:         $vote = "" | Select-Object v1,v2,v3,v4;

   5:         $vote.v1,$vote.v2,$vote.v3,$vote.v4 = $v.split(",")

   6:         $votes += $vote

   7:     } 

Looks nice and shiny until you run it :) I get the following error:

Method invocation failed because [System.Management.Automation.PSObject] doesn’t contain a method named ‘op_Addition’.

Not so shiny

The trick is that we $votes needs to be a collection of $vote objects, not another object identical to $vote.

So we instantiate $votes with a cast to [array] and life is good.

   1: $votes = @()

   2: foreach ($v in Get-Content votes.txt)

   3:     {    

   4:         $vote = "" | Select-Object v1,v2,v3,v4;

   5:         $vote.v1,$vote.v2,$vote.v3,$vote.v4 = $v.split(",")

   6:         $votes = $votes + $vote

   7:     } 

A quick update, thanks to Aleksandar. We should instantiate $votes as $votes = @(). I have updated the example above.

Comments 3 Comments »

Event six was a classic programming math problem. Find all the prime numbers in a given range. For this particular problem, the range was between 1 and 200.

Here’s my answer

   1: filter select-prime 
   2: { 
   3:     if ($_ -eq 1) {return $null}
   4:     for ($i=2;$i -le ([int][Math]::Sqrt($_));$i++) 
   5:         { 
   6:         if ($_ % $i -eq 0 ) {return}
   7:         }
   8:     $_
   9: } 
  10: 1..200 | select-prime

It turns out that in order to find if a prime number we can use the modulus operator. This math operator simply returns the remainder when one number is divided by another

If a number n % x  = 0 where x is not 1 or n, then the number will not be prime.

   1: PS U:> 6 % 2
   2: 0
   3: PS U:> 5 % 3
   4: 2
   5: PS U:> 5 % 2.5
   6: 0
   7: PS U:> 9 % 2
   8: 1
   9: PS U:> 9 % 3
  10: 0

So this will work if we just went from 2 to N. But it turns out that is way more work than necessary. We only need to go up to the Square Root of N and we can call it prime.

I really thought I was being super cool and iterated up the square root of the number. Turns out the calls into .NET to calculate the square root were to costly to make my efficiency efficient.

   1: PS C:usersandysDesktop> cat C:usersandysDesktopprimes.ps1
   2: filter select-primeQuickly
   3: {
   4:         if ($_ -eq 1) {return $null}
   5:         for ($i=2;$i -le ([int][Math]::Sqrt($_));$i++)
   6:                 {
   7:                 if ($_ % $i -eq 0 ) {return}
   8:                 }
   9:         $_
  10: }
  11:  
  12: filter select-primeSlowly
  13: {
  14:         if ($_ -eq 1) {return $null}
  15:         for ($i=2;$i -le $_;$i++)
  16:                 {
  17:                 if ($_ % $i -eq 0 ) {return}
  18:                 }
  19:         $_
  20: }
  21:  
  22: "Quickly"
  23: ""
  24: measure-command {1..200 | select-primeQuickly}
  25: "Slowly"
  26: ""
  27: measure-command {1..200 | select-primeSlowly}
  28: PS C:usersandysDesktop> C:usersandysDesktopprimes.ps1
  29: Quickly
  30:  
  31: Days              : 0
  32: Hours             : 0
  33: Minutes           : 0
  34: Seconds           : 0
  35: Milliseconds      : 734
  36: Ticks             : 7346876
  37: TotalDays         : 8.5033287037037E-06
  38: TotalHours        : 0.000204079888888889
  39: TotalMinutes      : 0.0122447933333333
  40: TotalSeconds      : 0.7346876
  41: TotalMilliseconds : 734.6876
  42:  
  43: Slowly
  44:  
  45: Days              : 0
  46: Hours             : 0
  47: Minutes           : 0
  48: Seconds           : 0
  49: Milliseconds      : 346
  50: Ticks             : 3469459
  51: TotalDays         : 4.0155775462963E-06
  52: TotalHours        : 9.63738611111111E-05
  53: TotalMinutes      : 0.00578243166666667
  54: TotalSeconds      : 0.3469459
  55: TotalMilliseconds : 346.9459

Looking at the results, what I thought was going to be quick was actually a lot slower! The version that calls into .NET ran in 736 milliseconds and the version that did not ran in 346 milliseconds.

Goes to show that when you are programming or scripting, what we think may be a gain in efficiency, may very well not be, as I learned in this exercise.

Comments 4 Comments »

I thought event 2 was pretty cool.  We have to take in a list of figure skaters and their scores, drop the highest and lowest score, average them, and assign a gold, silver and bronze medal to the top three competitors.

This problem was extremely conducive to using the power of the pipeline in Powershell.

   1: $competitors = Get-Content c:\scripts\skaters.txt 
   2: [array]$totalScores = "" | Select-Object Name,Score,Medal
   3:  
   4: foreach ($competitor in $competitors)
   5: {
   6:     $scores = "" | Select-Object Name,Score,Medal
   7:     $scores.Name,$scores.Score = $competitor.Split(",")
   8:     $stats = $scores.Score | Measure-Object -Maximum -Minimum
   9:     $scores.Score = $scores.Score -replace $stats.Maximum
  10:     $scores.Score = $scores.Score -replace $stats.Minimum
  11:     $scores.Score = [math]::Round(($scores.Score | Measure-Object -Average).Average,2)
  12:     $totalScores += $scores
  13: }
  14: $winners = $totalScores | Sort-Object Score -Descending | Select-Object -First 3 $winners[0].Medal,$winners[1].Medal,$winners[2].Medal = "Gold","Silver","Bronze"
  15: $winners | Format-Table -AutoSize

First we pull in the contents of the skaters.txt file. Next I use a little shortcut to create a custom PSObject using select-object. This creates a PSObject with three properties, a Name, a Score, and a Medal. This will become a collection of scores objects.

Using a foreach loop, we go through each competitor and create a new score object, assigning its values.Name to the name of the competitor and then the scores as an array in the .score property.

Please check out my post on multi variable assignment to see how this works.

I can pull the minimum and maximum using measure object and drop them. After that I use measure-object again to take the average and stuff it in the .score property.

I should mention here that /\/\o\/\/’s solution was pretty darn slick. I loved how he sorted and then select 1..5, essentially dropping the top and bottom score before he took the average.

The final item is to add the current score object to the collection of score objects called $totalScores.

Now it becomes quite easy to get the winners. We just sort the $totalScores collection by Score, select the top 3, and assign their .medal value accordingly.

Multi variable assignment is so cool. Ever since I found out about it it seems to appear in everything I do.

 

Have fun Powershelling

 

Andy

Comments No Comments »

Now that the answers have been posted for the first 2 events, I thought I would share my answer for the first event.

My first version worked but on some queries would take up to 20 plus minutes to crunch to through the 32,000 + words.

Here is what I finally ended up with:

   1: $wordlistlocation = "c:\scripts\wordlist.txt"
   2: $phonenumber = Read-Host "Please Enter a phone number"
   3: $numbers = [char[]]$phonenumber.ToString()
   4:  
   5: filter Get-Letters {
   6:  
   7:     switch ($_) {
   8:         2 {$letters = "ABC"}
   9:         3 {$letters = "DEF"}
  10:         4 {$letters = "GHI"}
  11:         5 {$letters = "JKL"}
  12:         6 {$letters = "MNO"}
  13:         7 {$letters = "PRS"}
  14:         8 {$letters = "TUV"}
  15:         9 {$letters = "WXY"} 
  16:     } # Switch
  17:  
  18:     return $letters
  19:  
  20: } #Get-Letters
  21:  
  22: $first,$second,$third,$fourth,$fifth,$sixth,$seventh = $numbers | Get-Letters
  23:  
  24: $string1 = $first[0]     + $first[1]     + $first[2]
  25: $string2 = $second[0]     + $second[1]     + $second[2]
  26: $string3 = $third[0]     + $third[1]     + $third[2]
  27: $string4 = $fourth[0]    + $fourth[1]    + $fourth[2]
  28: $string5 = $fifth[0]    + $fifth[1]        + $fifth[2]
  29: $string6 = $sixth[0]    + $sixth[1]        + $sixth[2]
  30: $string7 = $seventh[0]    + $seventh[1]    + $seventh[2]
  31:  
  32: $pattern = "^[" + $string1 + "][" + $string2 + "][" + $string3 + "][" + $string4 +"][" + $string5 + "][" + $string6 + "][" + $string7 + "]"
  33:  
  34: Get-Content $wordlistlocation | Select-String -Pattern $pattern | Select -first 1

First I get the content of the wordlist file and then read in a phone number. The cast to an array of char[] was necessary to put them into an array as individual elements. It was interesting that I could not cast to an array of ints, but time was short and I was under pressure, and it worked :)

Next, I used switch to build a filter that returns the possible letters for any given number. If I pass in a 2,  I get back a string of ABC.

Next I used multiple assignment to get all the possibilities for each number.

I used these to build a big ol’ regex pattern. It’s long but relatively simple. Basically just creates 7 chars using the [] for “this OR that OR the other thing.”

For example, if the first two numbers were 2 and 3, it would generate [abc][def] as the pattern.

Once I built up that pattern, I simply used get-content of the word list and piped that to Select-String and gave Select-String the the regex pattern I created.

In my first attempt, I created a list of all possibilities and then did a -match on them and compared to the word list file.

Using regex and Select-String brought my execution time down from 23 minutes to about 7 seconds, give or take.

Comments No Comments »