# Grouping all mathematical results using a number sequence with basic math operators

Not sure how to make the title smaller. I was reading the question A truly amazing way of making the number 2016 and some of the answers referred to programatically determining the answer set.

What I am doing is related to that in concept. Given a range of number 1 to X determine all possible mathematical expressions that can be used with those numbers and group the results together to see which whole numbers prevail.

function ConvertTo-Base
{
[CmdletBinding()]
param (
[parameter(ValueFromPipeline=$true,Mandatory=$True, HelpMessage="Base10 Integer number to convert to another base")]
[int]$Number=1000, [parameter(Mandatory=$True)]
[ValidateRange(2,20)]
[int]$Base ) [char[]]$alphanumerics = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"

do
{
# Determine the remainder
$Remainder = ($Number % $Base) # Get the associated character and add it to the beginning of the string.$newBaseValue = "$($alphanumerics[$Remainder])$newBaseValue"
# Adjust the number to remove the calculated portion
$Number = ($Number - $Remainder) /$Base
# Loop until we have processed the whole number
} while ($Number -gt 0) return$newBaseValue
}

# Variables
$maxRange = 3 #13 would make for the largest values 13!. Cap the script as 13$lowCountThreshold = 1         # Only show group results where the match exists more than five times.

# Mathematical Operators
[char[]]$operators = "+-*/" # Define the number range for calculations. 13 would make for the largest values 13!. Cap the script as 13$range = 1..$maxRange # Build the format string that will be used for invoking. Will look like 1{0}2{1}3. Acting as place holders for mathematic operators$formatString = -join (1..($range.count - 1) | ForEach-Object{"$_{$([int]$_ - 1)}"}) + $range[-1] # Determine the number of possible permutations of those operators inbetween the number set.$permutations = [System.Math]::Pow($operators.Count,$range.count - 1)

# Cycle each permutation
0..($permutations - 1) | ForEach-Object{ # Convert the number to a base equal to the element count in operators. Use those values to represent the index of the operators array.$mathString = $formatString -f @([string[]][char[]]((ConvertTo-Base -Number$_ -Base $operators.Count).PadLeft($range.count - 1,"0")) | ForEach-Object{$operators[[int]$_]})
# Build an object that contains the result and the mathematical expression
[pscustomobject]@{
Expression = $mathString Value = Invoke-Expression$mathString
}
# Since this take a while try and give the user some semblance of progress.
Write-Progress -Activity "Performing mathematical calculations" -Status "Please wait." -PercentComplete ($_ /$permutations * 100) -CurrentOperation "$([math]::Round($_ / $permutations * 100))% Completed." # Filter for whole number and only give group results } | Where-Object{$_.Value -is [int32]} | Group-Object Value | Where-Object{$_.Count -ge$lowCountThreshold} | Sort-Object Count -Descending


So if you were to run this and change the $maxValue to something smaller like 3 and change the you would Count Name Group ----- ---- ----- 2 6 {@{Expression=1+2+3; Value=6}, @{Expression=1*2*3; Value=6}} 1 0 {@{Expression=1+2-3; Value=0}} 1 7 {@{Expression=1+2*3; Value=7}} 1 2 {@{Expression=1-2+3; Value=2}} 1 -4 {@{Expression=1-2-3; Value=-4}} 1 -5 {@{Expression=1-2*3; Value=-5}} 1 5 {@{Expression=1*2+3; Value=5}} 1 -1 {@{Expression=1*2-3; Value=-1}}  So there are two operations that would get 6. 1+2+3 and 1*2*3. Who would have thought! If you are testing this be careful of using larger numbers here. Running something like a 9 would take about 7 minutes give or take since it would have 65536 permutations to figure out and then group. The function helps me convert each permutation number into its mathematical operator sequence. I take a number and convert it into base4. Then take that and use each number to pull an element out of the operator array. Then use the format operator to populate the string and use invoke expression to get the result. • I wanted to use mathematics tag but the description suggested that my math might be too simple for it so I tried a different and hopefully relevant tag. FYI im not hot on the title either but I dont know a shorter version of what I am doing here. – Matt Mar 12, 2016 at 4:30 ## 1 Answer About performance (there's a good article to get your started: Slow Code: Top 5 ways to make your Powershell scripts run faster); I apply that advice as follows: • Problem #1: Expensive operations repeated, e.g. • Invoke-Expression: rather than this time consuming cmdlet, I evaluate math expressions using the Compute method of the DataTable class; • Write-Progress: show progress bar. We don't call Write-Progress each time through the loop because that is slow. Edit: due to this Dangph's answer; • Problem #0: Not using cmdlet parameter filters, e.g. instead of collecting all the [System.Math]::Pow($operators.Count,$range.count-1) permutations and then narrowing the huge array using the Where-Object cmdlet, I collect only desired values; • Problem #4: Searching text: the original script computes a string in the (commonly usable) ConvertTo-Base function and then casts it as [string[]][char[]]; … and some further (maybe minor) enhancements are visible in the following (partially commented) 122635answer.ps1 script: # [CmdletBinding(PositionalBinding=$false)] # slows script execution cca 50%
param (     # Variables
[parameter()]                  # [ValidateRange(3,20)] # ???
[int]$maxRange = 9, [parameter()] [int]$lowCountThreshold = 5,
[parameter()]
[ValidateNotNullOrEmpty()]
[string]$opString = '+-*/' # Mathematical Operators as string ) Begin { Set-StrictMode -Version Latest # cast$operators variable as an array of characters
$operators = [char[]]$opString
$opsCount =$operators.Count
# Define the number range for calculations. 13 would make for the largest values 13!. Cap the script as 13
$maxRangeMinus1 =$maxRange - 1
# Build an array for extending
$maxOpsArray = 1..$maxRange
for ( $i=0;$i -lt $maxRange;$i++ ) {
$maxOpsArray[$maxRangeMinus1 -$i] = ,$operators[0] * $i } # Build the format string that will be used for invoking. # Will look like 1{2}2{1}3{0}4. Acting as place holders for mathematic operators [string]$formatString = -join (1..($maxRangeMinus1) | ForEach-Object{"$_{$([int]$maxRangeMinus1 - $_)}"}) +$maxRange # reverse order
# ForEach-Object{"$_{$([int]$_ - 1)}"}) +$maxRange  # $range[-1] # ascending order # ascending order would require [array]::Reverse($newOperatorArr) below in the process loop
if ( $maxRange -gt 11 ) { # force decimal computing in following $DataTable.Compute( $mathString, '')$formatString = $formatString.Replace('{','.0{') + '.0' } # Determine the number of possible permutations of those operators inbetween the number set. [int64]$permutations = [System.Math]::Pow($opsCount,$maxRangeMinus1)
# Narrow down $alphanumerics array size to necessary count$alphanumerics = $([char[]]'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ' )[0..($opsCount -1)]
Write-Verbose -Verbose -Message 
("maxRange=$maxRange, lowCountThreshold=$lowCountThreshold, operators=""$(-join$operators)"""
+ "rnpermutations=$permutations formatString=""$formatString""")
$DataTable=[System.Data.DataTable]::new()$Error.Clear()                             # for debugging purposes
}
Process {
# Cycle each permutation. Use for loop instead of 0..($permutations - 1) | ForEach-Object$( for ( $i=0;$i -lt $permutations;$i++ ) {
# Build an array of operators:
#        ( based on the number converted to base $opsCount )$Number = $i$newOperatorArr = @( $( do {$Remainder = $Number %$opsCount
# Get the associated character
$operators[$Remainder]
$Number = ($Number - $Remainder) /$opsCount
} while ($Number -gt 0) )) # Extend array of operators to appropriate length if necessary if ($newOperatorArr.Count -lt $maxRangeMinus1 ) {$newOperatorArr += $maxOpsArray[$newOperatorArr.Count]
}
### [array]::Reverse($newOperatorArr) # only if $formatString is in ascending order
$mathString =$formatString -f @( $newOperatorArr ) # evaluate math expression using the Compute method of the DataTable class # rather than time consuming Invoke-Expression$value122635 = $DataTable.Compute($mathString, '')
# Effectively reduce the output size in advance: refuse "non-integers"
if ( $value122635 -eq [System.Math]::Floor($value122635) ) {
# Build an object that contains the result and the mathematical expression
[pscustomobject]@{
Expression = $mathString Value = [System.Math]::Floor($value122635) # [int64]$value122635 } } # Show progress bar. We don't call Write-Progress each time through the loop # because that is slow. # Due to Dangph's answer https://codereview.stackexchange.com/a/221098/88771 if ($i % 1000 -eq 0) {
Write-Progress -Activity "Performing mathematical calculations" 
-Status "Please wait." -PercentComplete (100 * $i /$permutations)
-CurrentOperation "$([math]::Round(100 *$i / $permutations))% Completed." } # Only give group results } ) | Group-Object Value | Where-Object{$_.Count -ge $lowCountThreshold} | Sort-Object -property Count <# -Descending <##>, @{Expression = {[int]$_.Name} }


About correctness: the $_.Value -is [int32] condition seems to be too strong. For instance, 1/2*3*4 gives 6, however (1/2*3*4).GetTypeCode() gives Double; hence, (1/2*3*4) -is [int32] condition (incorrectly) rejects the value from result. For the sake of comparison, I added appropriate Write-Verbose cmdlet (immediately before the main loop) and used $maxRange = 7; $lowCountThreshold = 1 in the original script. The comparison of the latter with the adapted one I performed using the following wrapper: param ( [parameter()] [ValidateRange(8,13)] [int]$maxLoop = 12
)

$y = (Measure-Command {$x = D:\PShell\CR\122635.ps1}).TotalSeconds
$z = ($x | Measure-Object -Property  Count -Sum).Sum
'orig.  {0,4} {1,9} {2,9} {3,16}' -f 7, $x.count,$z, $y for ($icnt=7; $icnt -lt$maxLoop; $icnt++ ) {$y = (Measure-Command {
$x = D:\PShell\CR\122635answer.ps1 -maxRange$icnt -lowCountThreshold 1
}).TotalSeconds
$z = ($x | Measure-Object -Property  Count -Sum).Sum
'answer {0,4} {1,9} {2,9} {3,16}' -f $icnt,$x.count, $z,$y
if ($icnt -eq 7) {''} }  Comparison result shows that the 122635answer.ps1 script runs approximately 26× faster (21.33 / 0.80) than original for given $maxRange=7and \$lowCountThreshold=1 (in fact, I see no practical use if maxRange is greater than 9; that's in merely to show exponential time growth…):

pwsh -noprofile -file D:\PShell\CR\122635wrapper.ps1

VERBOSE: maxRange=7, lowCountThreshold=1, operators="+-*/"
permutations=4096 formatString="1{0}2{1}3{2}4{3}5{4}6{5}7"
orig.     7       269       756       21,3338469
VERBOSE: maxRange=7, lowCountThreshold=1, operators="+-*/"
permutations=4096 formatString="1{5}2{4}3{3}4{2}5{1}6{0}7"

VERBOSE: maxRange=8, lowCountThreshold=1, operators="+-*/"
permutations=16384 formatString="1{6}2{5}3{4}4{3}5{2}6{1}7{0}8"
VERBOSE: maxRange=9, lowCountThreshold=1, operators="+-*/"
permutations=65536 formatString="1{7}2{6}3{5}4{4}5{3}6{2}7{1}8{0}9"