10 December 2007

PowerShell Where-Object Predicates

In an earlier article about palindromes , I wrote an if-statement in the script block of the Where-Object cmdlet. While that's fine when the test is simple, the purpose of the Where-Object is obscured when the script block spans several lines, and it is not possible to re-use the code in the script block.

For example, let's find all prime numbers from a list of numbers. If you follow a top-down programming style, your code might look like this:

> 1..20 | Where-Object { Test-Prime $_ }

Now we have to supply a predicate Test-Prime that returns True if a number is prime, and False otherwise. Below is an example using Eratosthenes Sieve:

> function Test-Prime { 
  $isprime = $True
  foreach ($i in 2..([math]::Sqrt($args[0]))) {
    if ($args[0]%$i -eq 0) {
      $isprime = false
      break
    }
  }
  if ($isprime) {
    $args[1] += $args[0]
    return $True
  } else {
    return $False
  }
}

Test-Prime requires an array of existing primes, so the resulting script block is slightly different from our original plan. Oh well, can't plan everything in all the time. Let's make the modification and do a test run:

> $primes = (1,2); 1..20 | Where-Object { Test-Prime $_ $primes }
3
5
7
11
13
17
19

Not too shabby. It would be nice if Test-Prime can maintain its own list of prime numbers instead of relying on the caller to create it. I'll revisit this problem in future when I study more about PowerShell.