Get-RandomWeighted.ps1

<#PSScriptInfo
 
.VERSION
    1.0.0.2
 
.GUID
    cbd7128c-717b-4644-aab2-8617d00e5359
 
.AUTHOR
    SmallFoxx
 
.COMPANYNAME
    SmallFoxx
 
.COPYRIGHT
    The MIT License (MIT)
 
.TAGS
    Random Weighted Integer List
 
.LICENSEURI
    https://opensource.org/licenses/MIT
 
.PROJECTURI
    https://github.com/smallfoxx/poshstuff/blob/main/Get-RandomWeighted.ps1
 
.RELEASENOTES
 
#>


<#
.SYNOPSIS
Return a random result from weigthed values
.DESCRIPTION
Utilizing a key-value pair hashtable, the script will return a random
result. In the hashtable, the keys need to be the entries you are looking
randomize and their weights are the value of the pair.
.PARAMETER TableWeight
Key-value pair hashtable where the keys are the entries to be randomized
and the values are their respective weights. These weigthed values must
be integers unless the -Deeper switched is used. If the -Deeper switch
is utilized, any decimal based numerical value can be used for the weight.
.PARAMETER Iterations
The number of times to pull a random entry.
.PARAMETER Deeper
When used, the process will assign a specific range based on their
respective weights with a more accuracy.
.EXAMPLE
PS>$Colors = @{ "Red" = 5; "Blue" = 3; "Green" = 2}
PS>Get-RandomWeighted -TableWeight $Colors
 
Result will be a random color from Red, Blue, or Green. The results
should be 'Red' about 50% of the time, 'Blue' about 30%, and 'Green'
about 20%.
.EXAMPLE
PS>$Bob = Get-ADUser -Identity "BobSmith"
PS>$Chris = Get-AdUser -Identity "ChrisFields"
PS>$Debbie = Get-AdUser -Identity "DebbieReynolds"
PS>$Ecru = Get-AdUser -Identity "EcruTeam"
PS>$OnCallTable = @{
    $Bob = 0.0423
    $Chris = 0.5311
    $Debbie = 0.5284
    $Ecru = 0.3709
}
PS>Get-RandomWeighted -TableWeight $OnCallTable -Deeper
 
The Results will one of the 4 being picked based on weights. This means
'Bob' would be selected about 2.8%, 'Chris' about 36.1%, 'Debbie' about
35.9%, and 'Ecru' about 25.2%.
.EXAMPLE
PS>$Colors = @{ "Red" = 5; "Blue" = 3; "Green" = 2}
PS>Get-RandomWeighted -TableWeight $Colors -Iterations 10000 -Deeper | Group-Object | Select-Object -Property Name,Count
   
  Name Count
  ---- -----
  Blue 2930
  Green 2002
  Red 5068
 
This ran through a deeper randomization of the 3 colors 10,000 times,
grouped the results, and displayed the names and counts of each result.
Due to the nature of randomization, those counts will vary on each run
but should be close to 30%, 20%, and 50% for Blue, Green, and Red
respectively.
 
.LINK
Get-Random
#>
  
[CmdletBinding()]
param(
    [hashtable]$TableWeight,
    [int64]$Iterations=1,
    [switch]$Deeper
)

begin {
    Function New-Range {
        param(
            [parameter(ValueFromPipeline,ValueFromPipelineByPropertyName)]
            $Value,
            [parameter(ValueFromPipelineByPropertyName)]
            [decimal]$Weight=1
        )
        $PrevMaxRange = Get-MaxRange
        [PSCustomObject]@{
            "Value"=$Value
            "Weight"=$Weight
            "MinRange"=$PrevMaxRange
            "MaxRange"=$PrevMaxRange+$Weight
        }
    }
    Function Get-MaxRange {
        param(
            $RangeList = $script:ranges
        )
        [decimal](($RangeList | Measure-Object -Property MaxRange -Maximum).Maximum)
    }
    Function Get-WeightedResult {
        $WeightedResult = Get-Random -Minimum ([decimal]0) -Maximum (Get-MaxRange)
        $script:ranges | Where-Object {
            ($_.MinRange -lt $WeightedResult) -and ($_.MaxRange -ge $WeightedResult)
        }
    }
    Function Add-WeightToRange {
        param(
            $Value,
            [decimal]$Weight=1
        )
        If (-not $script:ranges) {
            $script:ranges = [System.Collections.ArrayList]@()
        }
        $ThisRange = New-Range -Value ($Value) -Weight ($Weight)
        $Null = $script:ranges.Add($ThisRange)
    }
}

process {
    If ($deeper) {
        ForEach ($key in $TableWeight.Keys) {
            Add-WeightToRange -Value $Key -Weight ($TableWeight.$Key)
        }
        1..$Iterations | ForEach-Object {
            $ResultEntry = Get-WeightedResult
            $ResultEntry.Value
        }
    } else {
        $array = [System.Collections.ArrayList]@()
        ForEach ($key in $TableWeight.Keys) {
            $null = 1..([int64]($TableWeight.$key)) | ForEach-Object { $array.Add($key) }
        }
        1..$Iterations | ForEach-Object {
            $array | Get-Random
        }
    } 
}