Skip to content

Instantly share code, notes, and snippets.

@nmbell
Last active April 20, 2021 01:17
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nmbell/0961a062091a266c1435087905de284e to your computer and use it in GitHub Desktop.
Save nmbell/0961a062091a266c1435087905de284e to your computer and use it in GitHub Desktop.
Iron Scripter\2021-02-09 Another PowerShell Math Challenge
function Get-NChooseK
{
<#
.SYNOPSIS
Returns all the possible combinations by choosing K items at a time from N possible items.
.DESCRIPTION
Returns all the possible combinations by choosing K items at a time from N possible items.
The combinations returned do not consider the order of items as important i.e. 123 is considered to be the same combination as 231, etc.
.PARAMETER ArrayN
The array of items to choose from.
.PARAMETER ChooseK
The number of items to choose.
.PARAMETER AllK
Includes combinations for all lesser values of K above zero i.e. 1 to K.
.PARAMETER Prefix
String that will prefix each line of the output.
.EXAMPLE
PS C:\> Get-NChooseK -ArrayN '1','2','3' -ChooseK 3
123
.EXAMPLE
PS C:\> Get-NChooseK -ArrayN '1','2','3' -ChooseK 3 -AllK
1
2
3
12
13
23
123
.EXAMPLE
PS C:\> Get-NChooseK -ArrayN '1','2','3' -ChooseK 2 -Prefix 'Combo: '
Combo: 12
Combo: 13
Combo: 23
.NOTES
Author : nmbell
#>
# Use cmdlet binding
[CmdletBinding()]
# Declare parameters
Param
(
# [ValidateCount(1,9)]
[ValidatePattern("^\d$")]
[String[]]
$ArrayN
, [Int]
$ChooseK
, [Switch]
$AllK
, [String]
$Prefix = ''
)
BEGIN
{
}
PROCESS
{
# Validate the inputs
$nMax = 9
$ArrayN = $ArrayN | Sort-Object -Unique
If ($ArrayN.Length -gt $nMax)
{
Write-Error "Can't choose from more than $nMax distinct items." -ErrorAction Stop
}
If ($ChooseK -gt $ArrayN.Length)
{
Write-Error "Can't choose $ChooseK items when only $($ArrayN.Length) are available." -ErrorAction Stop
}
# Track recursion
$Depth++
# Control the output
$firstK = If ($AllK) { 1 } Else { $ChooseK }
# Get combinations
$firstK..$ChooseK | ForEach-Object {
$thisK = $_
$ArrayN[0..($ArrayN.Length-($thisK--))] | ForEach-Object {
Write-Verbose ("Found ($Depth):"+(' '*$Depth)+$_)
If ($thisK -eq 0)
{
Write-Output ($Prefix+$_)
}
Else
{
Get-NChooseK -Array ($ArrayN[($ArrayN.IndexOf($_)+1)..($ArrayN.Length-1)]) -Choose $thisK -AllK:$false -Prefix ($Prefix+$_)
}
}
}
}
END
{
}
}
function Get-StringSum
{
<#
.SYNOPSIS
Gets the sum of the digits from an integer-only string.
.DESCRIPTION
Gets the sum of the digits from an integer-only string.
.PARAMETER InString
The input string of digits.
.PARAMETER CustomOut
Returns a custom object with details of the calculation.
.EXAMPLE
PS C:\>Get-StringSum -InString '1234'
10
.EXAMPLE
PS C:\>Get-StringSum -InString '1234' -CustomOut
Input Elements Calc Sum
----- -------- ---- ---
1234 4 1+2+3+4 = 10 10
.NOTES
Author : nmbell
#>
# Use cmdlet binding
[CmdletBinding()]
# Declare parameters
Param
(
[Parameter(
Mandatory = $true
, ValueFromPipeline = $true
)]
[ValidatePattern("^\d{1,10}$")]
[String]
$InString
, [Switch]
$CustomOut
)
BEGIN
{
}
PROCESS
{
$ints = $InString.ToCharArray() | ForEach-Object { [int]::Parse($_) }
$sums = $ints | Measure-Object -Sum
$calc = "$($ints -join '+') = $($sums.Sum)"
Write-Verbose "$InString`: $calc"
If ($CustomOut)
{
[PSCustomObject]@{
Input = $InString
Elements = $sums.Count
Calc = $calc
Sum = $sums.Sum
}
}
Else
{
$sums.Sum
}
}
END
{
}
}
@nmbell
Copy link
Author

nmbell commented Apr 13, 2021

Intermediate Level

Get-StringSum

Knowing that every string can be treated as an array of characters made the approach a lot simpler here.

PS C:\>Get-StringSum -InString '2568'-Verbose
VERBOSE: 2568: 2+5+6+8 = 21
21
PS C:\>Get-StringSum -InString '2568'-CustomOut

Input Elements Calc         Sum
----- -------- ----         ---
2568         4 2+5+6+8 = 21  21
PS C:\>Get-StringSum -InString '25685412472'
Get-StringSum : Cannot validate argument on parameter 'InString'. The argument "25685412472" does not match the "^\d{1,10}$" pattern. Supply an
argument that matches "^\d{1,10}$" and try the command again.
At line:1 char:25
+ Get-StringSum -InString '25685412472'
+                         ~~~~~~~~~~~~~
    + CategoryInfo          : InvalidData: (:) [Get-StringSum], ParameterBindingValidationException
    + FullyQualifiedErrorId : ParameterArgumentValidationError,Get-StringSum

Advanced Level

Get-NChooseK

This was a lot more challenging. Most of my brain power was used to come up with an algorithm that would handle the arrays correctly at each level of recursion.

  • The AllK switch is used to control whether output is only combinations of length ChooseK, or of length 1 through ChooseK.
  • The Prefix parameter is really an accumulator for the output strings, but has the effect that a value passed in for the initial call will actually prefix each line of output.
  • Validation
    • The array is ordered and uniquified within the body of the function, so I chose to apply validation after that operation i.e. 1-9 distinct elements
    • Without the uniquification operation, we could use parameter validation (i.e. [ValidateCount(1,9)]), which would give us combinations based on the item's position in the array, rather than its value.
    • I also included an additional validation to prevent non-sensical calls e.g. trying to choose combinations of 4 from 3 items.
  • There wasn't really a good place for Verbose output that didn't just repeat the output, so here it shows the traversal of the combination tree.
PS C:\>Get-NChooseK -ArrayN '2','5','6' -ChooseK 3
256
PS C:\>Get-NChooseK -ArrayN '2','5','6' -ChooseK 3 -AllK
2
5
6
25
26
56
256
PS C:\>Get-NChooseK -ArrayN '2','5','6' -ChooseK 3 -AllK -Prefix 'Combo: '
Combo: 2
Combo: 5
Combo: 6
Combo: 25
Combo: 26
Combo: 56
Combo: 256
PS C:\>Get-NChooseK -ArrayN '2','5','6' -ChooseK 3 -AllK -Verbose
VERBOSE: Found (1):  2
2
VERBOSE: Found (1):  5
5
VERBOSE: Found (1):  6
6
VERBOSE: Found (1):  2
VERBOSE: Found (2):    5
25
VERBOSE: Found (2):    6
26
VERBOSE: Found (1):  5
VERBOSE: Found (2):    6
56
VERBOSE: Found (1):  2
VERBOSE: Found (2):    5
VERBOSE: Found (3):      6
256
PS C:\>Get-NChooseK -ArrayN '2','5','16' -ChooseK 3
Get-NChooseK : Cannot validate argument on parameter 'ArrayN'. The argument "16" does not match the "^\d$" pattern. Supply an argument that matches
"^\d$" and try the command again.
At line:1 char:22
+ Get-NChooseK -ArrayN '2','5','16' -ChooseK 3
+                      ~~~~~~~~~~~~
    + CategoryInfo          : InvalidData: (:) [Get-NChooseK], ParameterBindingValidationException
    + FullyQualifiedErrorId : ParameterArgumentValidationError,Get-NChooseK
PS C:\>Get-NChooseK -ArrayN '1','2','3','4','5','6','7','8','9','0' -ChooseK 3
Get-NChooseK : Can't choose from more than 9 distinct items.
At line:1 char:1
+ Get-NChooseK -ArrayN '1','2','3','4','5','6','7','8','9','0' -ChooseK ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [Write-Error], WriteErrorException
    + FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,Get-NChooseK
PS C:\>Get-NChooseK -ArrayN '2','5','6' -ChooseK 4
Get-NChooseK : Can't choose 4 items when only 3 are available.
At line:1 char:1
+ Get-NChooseK -ArrayN '2','5','6' -ChooseK 4
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [Write-Error], WriteErrorException
    + FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,Get-NChooseK

So the final solution is to pipe the output of Get-NChooseK into Get-StringSum:

PS C:\>Get-NChooseK -ArrayN '2','5','6' -ChooseK 3 -AllK | Get-StringSum -CustomOut

Input Elements Calc       Sum
----- -------- ----       ---
2            1 2 = 2        2
5            1 5 = 5        5
6            1 6 = 6        6
25           2 2+5 = 7      7
26           2 2+6 = 8      8
56           2 5+6 = 11    11
256          3 2+5+6 = 13  13

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment