Get all combinations of an array - arrays

I'm currently trying to make a function that gets all possible combinations of array values.
I have come up with a non function version but it's limited to 3 values so i'm trying to make a function out of it to become more Dynamic
I tried searching SO but could not find a powershell example of what i was trying to do, i could find a PHP version but i'm very limited in my PHP
PHP: How to get all possible combinations of 1D array?
Non-function Script
$name = 'First','Middle','Last'
$list = #()
foreach ($c1 in $name) {
foreach ($c2 in $name) {
foreach ($c3 in $name) {
if (($c1 -ne $c2) -and ($c2 -ne $c3) -and ($c3 -ne $c1))
{
$list += "$c1 $c2 $c3"
}
}
}
}
This gives me the result
First Middle Last
First Last Middle
Middle First Last
Middle Last First
Last First Middle
Last Middle First
I'm not sure how i would rearrange the values when i'm recursing the function, this is what i have so far:
<#
.Synopsis
Short description
.DESCRIPTION
Long description
.EXAMPLE
Example of how to use this cmdlet
.EXAMPLE
Another example of how to use this cmdlet
#>
function Get-Combinations
{
[CmdletBinding()]
[OutputType([int])]
Param
(
# Param1 help description
[Parameter(Mandatory=$true,
ValueFromPipelineByPropertyName=$true,
Position=0)]
[string[]]$Array,
# Param1 help description
[Parameter(Mandatory=$false,
ValueFromPipelineByPropertyName=$false,
Position=1)]
[string]$Temp,
# Param1 help description
[Parameter(Mandatory=$false,
ValueFromPipelineByPropertyName=$true,
Position=2)]
[string[]]$Return
)
Begin
{
Write-Verbose "Starting Function Get-Combinations with parameters `n`n$($Array | Out-String)`n$temp`n`n$($Return | Out-String)"
If ($Temp)
{
$Return = $Temp
}
$newArray = new-object system.collections.arraylist
}
Process
{
Write-Verbose ($return | Out-String)
For($i=0; $i -lt $Array.Length; $i++)
{
#Write-Verbose $i
$Array | ForEach-Object {$newArray.Add($_)}
$newArray.RemoveAt($i)
Write-Verbose ($newArray | Out-String)
if ($newArray.Count -le 1)
{
Get-Combinations -Array $newArray -Temp $Temp -Return $Return
}
else
{
$Return = $Temp
}
}
$newArray
}
End
{
Write-Verbose "Exiting Function Get-Combinations"
}
}
$combinations = #("First","First2","Middle","Last")
$Combos = Get-Combinations -Array $combinations
$Combos
But the output i'm getting is all over the place
First2
Last
First2
Last
First
First2
Middle
Last
First
First2
Middle
Last
28/08 Update
Getting closer but still getting weird output
<#
.Synopsis
Short description
.DESCRIPTION
Long description
.EXAMPLE
Example of how to use this cmdlet
.EXAMPLE
Another example of how to use this cmdlet
#>
function Get-Combinations
{
[CmdletBinding()]
[OutputType([int])]
Param
(
# Param1 help description
[Parameter(Mandatory=$true,
ValueFromPipelineByPropertyName=$true,
Position=0)]
[string[]]$Array,
# Param1 help description
[Parameter(Mandatory=$false,
ValueFromPipelineByPropertyName=$false,
Position=1)]
[string]$Temp,
# Param1 help description
[Parameter(Mandatory=$false,
ValueFromPipelineByPropertyName=$true,
Position=2)]
[string[]]$Return
)
Begin
{
Write-Verbose "Starting Function Get-Combinations with parameters `n`n$($Array | Out-String)`n$temp`n`n$($Return | Out-String)"
If ($Temp)
{
$Return += $Temp
}
#$newArray = new-object [System.Collections.ArrayList]
#$Array | ForEach-Object {$newArray.Add($_) | Out-Null}
[System.Collections.ArrayList]$newArray = $Array
}
Process
{
Write-Verbose "return -> $return"
For($i=0; $i -lt $Array.Length; $i++)
{
Write-Verbose "`$i -> $i"
$element = $newArray[0]
$newArray.RemoveAt(0)
Write-Verbose "`$newArray -> $newArray"
Write-Verbose "Element -> $element"
if ($newArray.Count -gt 0)
{
Get-Combinations -Array $newArray -Temp (($temp + " " +$element).Trim()) -Return $Return
}
else
{
$Return = $Temp + " " + $element
}
}
$return
}
End
{
Write-Verbose "Exiting Function Get-Combinations"
}
}
$combinations = #("First","First2","Middle","Last")
$return = #()
$Combos = Get-Combinations -Array $combinations -Return $return
$Combos
New output (Yes there is a space before the 'Last' value, no i have no idea why)
First First2 Middle Last
First First2 Last
First Middle Last
First Last
First2 Middle Last
First2 Last
Middle Last
Last

Here is my solution:
function Remove ($element, $list)
{
$newList = #()
$list | % { if ($_ -ne $element) { $newList += $_} }
return $newList
}
function Append ($head, $tail)
{
if ($tail.Count -eq 0)
{ return ,$head }
$result = #()
$tail | %{
$newList = ,$head
$_ | %{ $newList += $_ }
$result += ,$newList
}
return $result
}
function Permute ($list)
{
if ($list.Count -eq 0)
{ return #() }
$list | %{
$permutations = Permute (Remove $_ $list)
return Append $_ $permutations
}
}
cls
$list = "x", "y", "z", "t", "v"
$permutations = Permute $list
$permutations | %{
Write-Host ([string]::Join(", ", $_))
}
EDIT: the same in one function (Permute). This is cheating a bit, however since I replaced plain functions whith lambdas. You could replace recursive calls with a stack you handle yourself, but that would make the code unecessarily complex ...
function Permute ($list)
{
$global:remove = {
param ($element, $list)
$newList = #()
$list | % { if ($_ -ne $element) { $newList += $_} }
return $newList
}
$global:append = {
param ($head, $tail)
if ($tail.Count -eq 0)
{ return ,$head }
$result = #()
$tail | %{
$newList = ,$head
$_ | %{ $newList += $_ }
$result += ,$newList
}
return $result
}
if ($list.Count -eq 0)
{ return #() }
$list | %{
$permutations = Permute ($remove.Invoke($_, $list))
return $append.Invoke($_, $permutations)
}
}
cls
$list = "x", "y", "z", "t"
$permutations = Permute $list
$permutations | %{
Write-Host ([string]::Join(", ", $_))
}

I tried to learn something new and help you out but Im stuck. maybe this will help you get in the right direction but I dont know enough about Powershell recursion to figure this out. I converted the php to powershell and in theory it should work but it doesnt.
$array = #('Alpha', 'Beta', 'Gamma', 'Sigma')
function depth_picker([system.collections.arraylist]$arr,$temp_string, $collect)
{
if($temp_string -ne ""){$collect += $temp_string}
for($i = 0; $i -lt $arr.count;$i++)
{
[system.collections.arraylist]$arrCopy = $arr
$elem = $arrCopy[$i]
$arrCopy.removeRange($i,1)
if($arrCopy.count -gt 0){
depth_picker -arr $arrCopy -temp_string "$temp_string $elem" -collect $collect}
else{$collect += "$temp_string $elem"}
}
}
$collect = #()
depth_picker -arr $array -temp_string "" -collect $collect
$collect
It seems to work and will get you the first set of possibles:
Alpha
Alpha Beta
Alpha Beta Gamma
Alpha Beta Gamma Sigma
But for some reason that I cant figure out when it gets back to the previous functions and does $i++ then checks ($i -lt $arr.count) $arr.count it always 0 so it never goes to the next iteration to continue finding the possibilities.
Hopefully someone else can fix what I cant seem to figure out as I dont know enough about recursion. But it seems that with each level of depth called the previous depth level $arr variable and values is lost.

Here is my solution with a recursive function. It generates space separated strings but it's quite simple to split each element with $list[$i].split(" "):
function Get-Permutations
{
param ($array, $cur, $depth, $list)
$depth ++
for ($i = 0; $i -lt $array.Count; $i++)
{
$list += $cur+" "+$array[$i]
if ($depth -lt $array.Count)
{
$list = Get-Permutations $array ($cur+" "+$array[$i]) $depth $list
}
}
$list
}
$array = #("first","second","third","fourth")
$list = #()
$list = Get-Permutations $array "" 0 $list
$list

The solution posted by Micky Balladelli almost worked for me. Here is a version that does not duplicate values:
Function Get-Permutations
{
param ($array_in, $current, $depth, $array_out)
$depth++
$array_in = $array_in | select -Unique
for ($i = 0; $i -lt $array_in.Count; $i++)
{
$array_out += ($current+" "+$array_in[$i]).Trim()
if ($depth -lt $array_in.Count)
{
$array_out = Get-Permutations $array_in ($current+" "+$array_in[$i]) $depth $array_out
}
else {}
}
if(!($array_out -contains ($array_in -Join " "))) {}
for ($i = 0; $i -lt $array_out.Count; $i++)
{
$array_out[$i] = (($array_out[$i].Split(" ")) | select -Unique) -Join " "
}
$array_out | select -Unique
}

Related

Slow array operations after DB resultset

I need help optimizing my PowerShell script.
$sorted = #()
$firsttime = 0
$j = 0
$zaehler = $results.Count-1
for ($i=0; $i -le $results.Count-1; $i++) {
$j = $i+1
while ($results.GUID[$i] -eq $results.GUID[$j]) {
$klassen = ""
$rec = $results | where {$_.GUID -eq $results.GUID[$i]}
if ($firsttime -eq 0 -or !$sorted.GUID.contains($rec[0].GUID)) {
$firsttime = 1
foreach ($item in $rec.Klasse) {
if ($klassen -eq "") {
$klassen += $item
} else {
if (!$klassen.Contains($item)) {
$klassen += "," + $item
}
}
}
$rec[0].Klasse = $klassen
$sorted += $rec[0]
}
$j = $j+1
}
Write-Host ($i/$zaehler).ToString("P") "von Schule" $schule
}
if (!$sorted) {
$results
} else {
$sorted
}
Basically in my resultset ($results) I got duplicate lines of teachers and the only difference is the class ("Klasse/Klassen") they are teaching at.
To minimize the output I am checking if the first GUID is the same as the second and then the script appends the second class to the first one. So the $sorted array has just one line per teacher with a comma-seperated string which shows all classes.
Sample line of $results:
#{
GUID={1234567-1234-1234-1234-1234567};
userrole=teacher;
Vorname=Max;
Nachname=Mustermann;
Geburtstag=01.01.2000;
Klasse=9A;
Schule=123456
}
#{
GUID={1234567-1234-1234-1234-1234567};
userrole=teacher;
Vorname=Max;
Nachname=Mustermann;
Geburtstag=01.01.2000;
Klasse=9B;
Schule=123456
}
Sample line of $sorted[0]:
#{
GUID={1234567-1234-1234-1234-1234567};
userrole=teacher;
Vorname=Max;
Nachname=Mustermann;
Geburtstag=01.01.2000;
Klasse=9A,9B,9C,5A;
Schule=123456
}
The sorting process (check if contains, foreach $item, add to $klassen) is pretty slow.
I would be very grateful for any kind of ideas how to optimize the script.
Maybe something like this would work:
$results | ForEach-Object {
New-Object -Type PSObject -Property $_
} | Group-Object GUID | ForEach-Object {
$teacher = $_.Group
$teacher | Select-Object -First 1 -Exclude Klasse -Property *, #{n='Klasse';e={
$teacher | Select-Object -Expand Klasse
}}
}
Convert your hashtables into custom objects, group them by GUID, then replace the original "Klasse" property with a new one containing an array of the values from all objects in that group, and select the first result.
Should $results already be a list of objects you can skip the first ForEach-Object:
$results | Group-Object GUID | ForEach-Object {
$teacher = $_.Group
$teacher | Select-Object -First 1 -Exclude Klasse -Property *, #{n='Klasse';e={
$teacher | Select-Object -Expand Klasse
}}
}

Why is my array being read as null? What am I missing?

I am working on PowerShell script that will check server services. I keep getting a "Cannot index into a null array."
The error references the second if statement "if ($select_string_result.Line[$select_string_result.Line.Length-1] -eq '1')"
The object type of "$select_string_result" is displayed as Array and the txt file has data but the script will not process through it.
The "Line" property of the array records as null and the "Length" is recorded as 0.
$filepathserver = 'Path'
$filepathlocal = 'Path'
function Get-Timestamp
{
return Get-Date -Format "MM/dd/yyyy hh:mm:ss tt"
}
function refresh-data
{
# Pulls Services and Services Status
$orionData = Get-SwisData $swis "SELECT ComponentID, StatusDescription FROM Orion.APM.Component"
# Sends output to a txt file
$orionData | Out-File "$filepathlocal\All_App_Services.txt"
}
function check-status($select_string_result)
{
if ($select_string_result.Line -isnot [system.array])
{
if ($select_string_result.Line[$select_string_result.Line.Length-1] -eq '1')
{
return 100
}
else
{
return 0
}
}
else
{
$sum = 0.0
$add = 100.0/$select_string_result.Length
foreach ($match in $select_string_result)
{
if ($match.Line[$match.Line.Length-1] -eq '1')
{
$sum += $add
}
}
if ($sum -lt 100) {$sum = 0} # this line collapses the values in to either 0 or 100
$sum = [int][Math]::Ceiling($sum)
return $sum
}
}
function main
{
refresh-data
# Filters for Application specific Services
$f = #("94944 ", "94945 ", "94951 ", "94946 ", "94942 ", "94948 ", "94949 ", "94950 ", "94943 ", "94947 ", "94952 ", "94953 ")
$AppServices = Get-Content "Path" | Select-String $f
$AppServices | Set-Content "Path"
#Removes leading spaces from array
(Get-Content "$filepathlocal\File.txt") -replace "Up","1" | % {$_.trim()} | Out-File "$filepathlocal\File.txt"
$AppServices = Get-Content "$filepathlocal\File.txt"
$AppServices.GetType()
# Writes status of each group to .txt file
$logfile= "$filepathserver\ServicesStatus.txt"
$t = Get-Timestamp
$v = check-status $AppServices
$s = "$t|Application-Services|$v"
$s | Out-File $logfile -Append -Encoding "UTF8"
$s
}
main
$select_string_result.Line resolves to $null because the array of strings that you get from Get-Content does not have a Line property, so the if statement should look more like:
if($select_string_result[$select_string_result.Length - 1] -eq '1') { ... }
PowerShell also allows you to address the last index with just -1, allowing us to simplify the statement as:
if($select_string_result[-1] -eq '1') { ... }
That being said, rather than attempting to check whether the parameter passed to a function is an array or not, you'd want to declare the parameter an array in the first place and then use a foreach loop over it:
function check-status([string[]]$select_string_result)
{
$sum = 0.0
$add = 100.0/$select_string_result.Length
foreach ($match in $select_string_result)
{
if ($match[-1] -eq '1')
{
$sum += $add
}
}
if ($sum -lt 100) {$sum = 0} # this line collapses the values in to either 0 or 100
$sum = [int][Math]::Ceiling($sum)
return $sum
}
much nice, way less code.
Now, instead of attempting to index into the string, I'd suggest using the -like wildcard operator or the -match regex operator to check whether each string ends with 1:
if ($match -like '*1')
{
$sum += $add
}
Since $sum is always exactly 100, or otherwise gets reset to 0, the call to [Math]::Ceiling() is redundant and can be removed:
function check-status([string[]]$select_string_result)
{
$sum = 0.0
$add = 100.0/$select_string_result.Length
foreach ($match in $select_string_result)
{
if ($match -like '*1')
{
$sum += $add
}
}
if ($sum -lt 100) {$sum = 0} # this line collapses the values in to either 0 or 100
return $sum
}
If you look carefully at the function as implemented, you'll notice that the only case in which 100 is returned is when all strings in $select_string_result end in 1.
We can easily test for this by using the -like operator directly on our input array, it will act as a filter operator:
function check-status([string[]]$select_string_result)
{
if(#($select_string_result -like '*1').Count -eq $select_string_result.Count)
{
$sum = 100
}
else
{
$sum = 0
}
return $sum
}
Now, another way of asserting that all strings in the array end in 1, is to simply test whether no string does not end in 1:
function check-status([string[]]$select_string_result)
{
if(#($select_string_result -notlike '*1'))
{
$sum = 0
}
else
{
$sum = 100
}
return $sum
}
Now all we need to do is shine it up a bit, like change check to a more appropriate verb and we've got a much nicer, short or powershell-idiomatic function :-)
function Measure-StatusValue
{
param(
[ValidateNotNullOrEmpty()]
[string[]]$StatusStrings
)
if(#($StatusStrings -notlike '*1'))
{
return 0
}
return 100
}

How to add a number at end of username

I need to generate usernames that start with a letter of the alphabet, followed by the date and that ends with numbers from 1 to 20. For a total of 520 names.
Here's the variables I came up with:
$letter = 97..122 | foreach {[char]$_}
$date = get-date -UFormat %a%y%d
$name = $letter | ForEach-Object { $_ + $date }
So I managed to put my letters and my dates together, to form names like aWed1513, which is exactly what I need, but when it comes to make each to end with numbers, I'm stuck. Tried with a For loop by adding incrementing $i to $name, but doesn't work..
It seems like you're just about there. Your for loop should look like this:
$names = #()
$letter = 97..122 | foreach {[char]$_}
$date = get-date -UFormat %a%y%d
for($i = 1; $i -le 20; $i++) {
$names += $letter | ForEach-Object { '{0}{1}{2}' -f $_, $date, $i }
}
$names
An alternate approach to the answer given that just adds another ForEach-Object to the OP's original code:
$letter = 97..122 | foreach {[char]$_}
$date = get-date -UFormat %a%y%d
$names = $letter | ForEach-Object { $_ + $date } | ForEach-Object { for($i=1;$i -le 20;$i++) { $_ + $i } }
And to shorten to get same result:
$letter = 97..122 | foreach {[char]$_}
$date = get-date -UFormat %a%y%d
$names = $letter | ForEach-Object { for($i=1;$i -le 20;$i++) { $_ + $date + $i } }

Foreach in foreach login in powershell

How can I make this logic work in powershell?
$i = ("SharePointCDPTestProject1_Feature1", "SharePointCDPTestProject1_Feature2", "SharePointCDPTestProject1_Feature3");
$a = Get-SPSite http://localhost/sts*;
Foreach($id in $i)
{
Foreach($p in $a)
{
Enable-SPFeature –identity $id -URL $p
}
}
Sorry.. I'm noob in ps
$i = ("SharePointCDPTestProject1_Feature1", "SharePointCDPTestProject1_Feature2", "SharePointCDPTestProject1_Feature3");
$a = Get-SPSite http://localhost/sts*;
$i | Foreach{
#save current id
$id=$_
$a | Foreach{
Enable-SPFeature –identity $id -URL $_
}
}

How to append to powershell Hashtable value?

I am interating through a list of Microsoft.SqlServer.Management.Smo.Server objects and adding them to a hashtable like so:
$instances = Get-Content -Path .\Instances.txt
$scripts = #{}
foreach ($i in $instances)
{
$instance = New-Object Microsoft.SqlServer.Management.Smo.Server $i
foreach($login in $instance.Logins)
{
$scripts.Add($instance.Name, $login.Script())
}
}
So far so good. What I want to do now is append a string to the end of the hashtable value. So for an $instance I want to append a string to the hashtable value for that $instance. How would I do that? I have started with this, but I'm not sure if I'm on the right track:
foreach ($db in $instance.Databases)
{
foreach ($luser in $db.Users)
{
if(!$luser.IsSystemObject)
{
$scripts.Set_Item ($instance, <what do I add in here?>)
}
}
}
Cheers
$h= #{}
$h.add("Test", "Item")
$h
Name Value
---- -----
Test Item
$h."Test" += " is changed"
$h
Name Value
---- -----
Test Item is changed
I would go with this code.
$instances = Get-Content -Path .\Instances.txt
$scripts = #{}
foreach ($i in $instances)
{
$instance = New-Object Microsoft.SqlServer.Management.Smo.Server $i
foreach($login in $instance.Logins)
{
$scripts[$instance.Name] = #($scripts[$instance.Name]) + $login.Script().ToString()
}
}
.
foreach ($db in $instance.Databases)
{
foreach ($luser in $db.Users)
{
if(!$luser.IsSystemObject)
{
$scripts[$instance] = #($scripts[$instance]) + $luser.Script().ToString()
}
}
}
The result will be a hash table with each instance as a key, and an array of strings where each string is the T-SQL script for a user.
The .Script() method returns a string collection. There's probably a more elegant way of doing it, but replacing
$scripts.Set_Item ($instance, <what do I add in here?>)
with
$val = $scripts[$instance]
$val.Add("text to add")
$scripts.Set_Item($instance, $val)
should work.
$test = #{}
$test.Hello = "Hello World"
Write-Host "message from $($test.Hello)"
$test.Hello += " Cosmonaut"
Write-Host "message from $($test.Hello)"

Resources