
Windows PowerShell solution to Event 6 in the 2008 Winter Scripting Games.
Event 6 turned out to be a little easier than anticipated, at least for the Scripting Guys. When we first read up on the Sieve of Eratosthenes we thought, “Good heavens, no one will ever be able to solve this event!” As it turned out, however, the problem was that the explanation of the Sieve of Eratosthenes was needlessly complicated; the underlying concept was actually fairly easy to understand, and even easier to mimic in Windows PowerShell. How easy? This easy:
$arrNumbers = @()
for ($i = 1; $i -le 200; $i++)
{$arrNumbers += $i}
$arrNumbers[0] = "Not prime"
$strPrimeNumbers = "2`n"
$intDivisor = 2
do
{
for ($j = 0; $j -le $arrNumbers.length - 1; $j++)
{
if ($arrNumbers[$j] -ne "Not prime")
{
$intResult = $arrNumbers[$j] % $intDivisor
if ($intResult -eq 0)
{$arrNumbers[$j] = "Not prime"}
}
}
for ($i = 2; $i -le $arrNumbers.length; $i++)
{
if ($arrNumbers[$i] -ne "Not prime")
{
$intDivisor = $arrNumbers[$i]
$strPrimeNumbers = $strPrimeNumbers + $intDivisor + "`n"
break
}
}
}
until ($intDivisor -ge $arrNumbers.length - 1)
$strPrimeNumbers
As you can see, the script we came up with to solve Event 6 is one of the shorter scripts in the Advanced Division; at the same time, it’s also one of the more-confusing scripts, at least at first glance. With that in mind, let’s see what we can do to clear up some of the confusion.
Things actually start off simple enough; in line 1 we use this bit of code to create an empty array named $arrNumbers:
$arrNumbers = @()
As soon as we have our empty array in hand we then use a for loop to add the numbers 1 through 200 to this array:
for ($i = 1; $i -le 200; $i++)
{$arrNumbers += $i}
We’re going to use this array to keep track of which numbers are prime numbers and which ones aren’t. By definition, the number 1 is not a prime number; hence we set the value of array item 0 to the string Not prime:
$arrNumbers[0] = "Not prime"
Note. Why array item 0? Well, remember, the first item in an array always have the index number 0. The number 1 was the first item we added to our array; that means that the number 1 has an index number of 0. This is a trick we’ll have to rely on throughout this script. Do we need to access the number 42? We can find that number at array item 41: the number minus 1. |
Also by definition, the number 2 is a prime number. Hence we add the number 2 and a carriage return-linefeed to a string value named $strPrimeNumbers:
$strPrimeNumbers = "2`n"
Our next step is to assign the value 2 to a variable named $intDivisor. Why? Well, we decided to tackle this problem using the Sieve of Eratosthenes. This algorithm starts with the premise that 1 is not a prime number but that 2 is. From there the Sieve takes the number 2 and divides it into all the other numbers in our collection. If 2 divides evenly into a number then we know that this number is not prime (prime numbers can only be evenly divided by 1 and themselves). In turn, we remove this number from the list of potential prime numbers, something we do by marking the value as Not prime in our array.
Now, what if 2 doesn’t divide evenly into a number (for example, 2 does not divide evenly into 9). Does that mean that 9 is a prime number?
Well, maybe but, then again, maybe not. That’s why, instead of stopping after we divide all the numbers by 2, we find the smallest number that could not be evenly divided by 2 (which happens to be 3). We tag 3 as a prime number, then divide all the remaining numbers by 3. This continues until we’ve divided all the numbers in our collection by all the prime numbers that precede it.
Confusing? Hey, don’t blame us; blame Eratosthenes!
Anyway, that’s why we set $intDivisor to 2: to find all the prime numbers between 1 and 100 we have to first try to divide all those values by 2.
To do that, we set up a do loop designed to run until $intDivisor is greater than or equal to the Length of our array minus 1:
until ($intDivisor -ge $arrNumbers.length - 1)
How did we come up with that crazy criteria? Well, we have 200 items in our array; that means that the length is equal to 200. That also means that the largest index number in the array is 199. (The largest index number, or upper bound, is always one less than the total number of items in the array.) Thus we want to continue looping until our divisor hits 200; when that happens, there are no more items left in the array.
Inside the loop, we set up a for loop that runs through all the items in the array (items 0 through 199):
for ($j = 0; $j -le $arrNumbers.length - 1; $j++)
For each item in the array we start by checking to see if the value of that item is the string Not prime:
if ($arrNumbers[$j] -ne "Not prime")
If the value is equal to Not prime that means we’ve already determined that this is not a prime number; consequently, we skip that item, go back to the top of the loop, and try the next number. If the item is not equal to Not prime we execute the following block of code:
$intResult = $arrNumbers[$j] % $intDivisor
if ($intResult -eq 0)
{$arrNumbers[$j] = "Not prime"}
In the first line we used the modulus operator (%) to divide $intDivisor into array item $j. The modulus operator is designed to divide two numbers and then return only the remainder. If the remainder is equal to 0 (line 2) that means that the number can be evenly divided by $intDivisor. And because that also means that the number is not a prime number, we use line 3 in the code block to change the value of the array item to Not prime.
And then we repeat the process with the next array item.
That takes care of dividing all the numbers in the array by 2. Now we need to divide all those numbers by the next prime number. How do we know which is the next prime number? Well, that’s what our next for loop is for. Here we loop through the entire array, looking for the first item that is not equal to the string Not prime (in other words, the first item that’s still a number). What do we do when we find this value? Three things:
$intDivisor = $arrNumbers[$i] $strPrimeNumbers = $strPrimeNumbers + $intDivisor + "`n" break
In line 1, we’re assigning the value of this number to $intDivisor. That means that, the next time through our do loop, we’ll divide all the numbers in the array by 3. In line 2, we add this number and a carriage return-linefeed to the string $strPrimeNumbers; that’s because we know that this value is a prime number. Finally, we use the break command to break out of the for loop and return to the do loop.
Whew!
The rest is easy; in fact, once our do loop ends all we have to do is echo back the value of $strPrimeNumbers and we’ll have our list of prime numbers. If all goes well, that list should look like this:
2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97 101 103 107 109 113 127 131 137 139 149 151 157 163 167 173 179 181 191 193 197 199
And guess what? In a Scripting Guys first, and as near as we can tell, everything did go well.