Unique Lottery Numbers With Advanced Array Functionality

Recently in a workshop, we wanted to create a little PowerShell script to return nine unique random numbers. Since all participants were experienced VBScript scripters, they quickly came up with a solution. This solution grew bigger and bigger and more complicated with every new detail ("the random numbers need to be unique").

Finally, we created a little PowerShell script which I want to share with you. It touches a number of efficient PowerShell strategies and demonstrates why PowerShell code can be very short and efficient and why it does not always pay off to translate older scripts 1:1 to PowerShell.

Getting Random Numbers

The first thing we need are random numbers. Since there is no built-in cmdlet that provides random numbers, PowerShell allows you to "cross the red line of managed commands" and enter the wild and wonderful world of the developers by using .NET code directly.

The hardest thing here is to know the name of the .NET type providing the kind of service you are looking for. The .NET type System.Random is the random number generator you need, and once you derive an instance (a real object) from that type, you can use the Next() method to create random numbers:

PS> $random = New-Object System.Random
PS> $random.Next()
279622301
PS> $random.Next(1,6)
2

Next() actually gives you a true random number. To scale this random number into a specific value range, add the minimum and maximum value you want to use. Next(1,6) is your electronic dice and returns a random number between 1 and 6.

Getting 9 Random Numbers

Since we need more than one random number, what we need next is a loop. To return nine random numbers out of 49, you could use a for loop like this:

PS> for ($x=1; $x -le 9; $x++) { $random.Next(1,49) }
31
17
40
48
11
39
19
23
25

This kind of loop uses a counter variable $x which is initialized to 1 and then incremented by 1 ($x++). The loop continues to execute the script block while the condition is met ($x -le 9). A more readable form of this loop could look like this:

PS> 1..9 | ForEach-Object { $random.Next(1,49) } | Sort-Object
12
20
21
22
22
28
28
39
42

Note how Sort-Object sorts the result as well. This reveals a flaw in this approach: there can be duplicate random numbers in the result since each drawing is independent of the previous drawings.

Getting Unique Random Numbers

To get a list of truly unique random numbers, we need to keep track of the numbers we already drew to make sure each new random number is new.

There are plenty of ways to do this but a very easy way is to use a PowerShell array. We need to change the loop construct as well since this time, we do not know beforehand how many random numbers we need to draw (depending on how many duplicate entries we draw).

Whenever you need to take care of runtime environments you should choose the do...while loop. It loops the script block until some exit condition is met. Let's first draw nine random numbers and place them in a result array:

$random = New-Object System.Random
$result = @()

do {

$result += $random.Next(1,49)

} while ($result.count -le 8)

$result = $result | Sort-Object
$result

@() creates a new empty array. Unlike other script languages, PowerShell makes it very easy to add additional elements to that array. Simply assign a new value to the array, and it grows by that element.

The loop runs until the $result array contains more than 8 elements. Note that this is a footer-based loop. In case you need a header-based loop where the loop condition is checked before the loop enters, replace the do statement with your while statement.

At the end of this little script, the array is sorted using Sort-Object and then outputted.

However, this script still can deliver duplicate random numbers. So we still need a way of finding out whether a random number was already drawn. This job is done by the -contains operator. It checks whether an array already contains an entry:

$random = New-Object System.Random
$result = @()

do {

$randomnumber = $random.Next(1,49)
if ($result -notcontains $randomnumber) {
$result += $randomnumber
}


} while ($result.count -le 8)

$result = $result | Sort-Object
$result

To find out whether the $result array already contains your newly drawn random number, the new random number is first stored in a separate variable ($randomnumber). Next, a condition is used (If). It checks whether or not the array contains the new random number.

If it does not contain the number (-notcontains), the script block after the condition is executed and adds the new number to your array. Since the loop continues until the array contains nine elements, you do not need to change anything here. The loop will simply collect new random numbers until you have successfully drawn nine unique numbers.

Creating A Function

Finally, we can wrap up the code as a new function like Get-RandomNumbers. The following function returns any number of (unique) random numbers in a variable range:

function Get-RandomNumbers($minimum = 1, $maximum = 49, $number = 9) {
$random = New-Object System.Random
$result = @()

do {

$randomnumber = $random.Next(1,49)
if ($result -notcontains $randomnumber) {
$result += $randomnumber
}


} while ($result.count -lt $number)

$result = $result | Sort-Object
$result
}

So to get five unique random numbers between 10 and 100, call it like this:

PS> Get-RandomNumbers -min 10 -max 100 5
21
24
31
46
47
PS>

Learning Points

Whenever you need a functionality not provided by "managed" Cmdlets, you can resort to the .NET framework. Simply go to google and search for whatever you need. Chances are you will find the name of the .NET type you need to do the job. Random numbers can be generated with the help of the .NET type System.Random. PowerShell Plus will provide you with a full list of all available .NET types and also provides code completion for the methods like Next().

There are two basic loop constructs. For loops are used when you know at design time how often the loop needs to execute. You can either use the For() statement and use a counter variable, or you can create loops using the pipeline and Foreach-Object. Whenever you do not know at design time how often the loop needs to run, use the Do...While or Do...Until loop. Any loop can be dangerous when you provide a wrong exit condition that never exits the loop.

If you need to execute code based on a certain condition, use If. Only when the condition you specified evaluates to $true will the script code be executed. This is how the example adds new random numbers to the array.

Arrays are a great way of storing and examining results. To create an empty array, use the @() construct. There are a number of powerful enhancements to arrays in PowerShell. For example, you can simply add new elements to let the array grow. Each array has a Count property which tells you how many elements are stored inside the array, and the -contains and -notcontains comparison operators check whether the array already contains some given value.

Merry Xmas to all

-Tobias


Posted Dec 22 2008, 06:51 PM by Tobias Weltner

Comments

Jonathan Noble wrote re: Unique Lottery Numbers With Advanced Array Functionality
on 12-23-2008 6:48 AM

It's worth making it clear that if you want random numbers "between" 1 and 49, which includes the number 49, you need to do $random.Next(1,50). If you do a crude check:

$random = New-Object System.Random

1..1000 | %{$random.Next(1,49)} | group | sort name

You'll see that you never get 49. $random.Next(1,49) returns the numbers 1-48, which may be exactly what you wanted, but it may confuse some people. I blogged about it here: http://tinyurl.com/8be66j

Tobias Weltner wrote re: Unique Lottery Numbers With Advanced Array Functionality
on 12-23-2008 9:01 AM

oops good catch!

Windows PowerShell Blog wrote Dreaming In PowerShell V2 : Lottery Numbers with Get-Random
on 12-23-2008 9:28 PM

Tobias Weltner writes a blog called Dreaming In PowerShell, and he recently posted a way to get a list

James Brundage wrote re: Unique Lottery Numbers With Advanced Array Functionality
on 12-23-2008 9:30 PM

I just posted a CTP3 version of this function that uses Get-Random.  It has inline help, which makes it a little longer, but the core function is amazingly short:

   $minimum..$maximum |

       Get-Random -Count $number |

       Sort-Object

Tobias Weltner wrote re: Unique Lottery Numbers With Advanced Array Functionality
on 12-24-2008 2:52 AM

Great, nice post in the PS team blog!

Dew Drop - Xmas Edition - December 24-25, 2008 | Alvin Ashcraft's Morning Dew wrote Dew Drop - Xmas Edition - December 24-25, 2008 | Alvin Ashcraft's Morning Dew
on 12-25-2008 8:37 PM

Pingback from  Dew Drop - Xmas Edition - December 24-25, 2008 | Alvin Ashcraft's Morning Dew

Русский блог команды разработки Powershell wrote Мечтая о PowerShell V2 : случайные числа и Get-Random
on 03-24-2009 3:01 PM

Тобиас Вельтнер (Tobias Weltner) пишет блог под названием Dreaming In PowerShell (Мечтая о PowerShell)

Copyright 2012 PowerShell.com. All rights reserved.