Display multiple array values in list in Powershell - arrays

I have multiple arrays like this:
$arrayList1 = #()
$arrayList2 = #()
$arrayList1 += "james"
$arrayList1 += "henry"
$arrayList1 += "bob"
$arrayList2 += "scott"
$arrayList2 += "john"
$arrayList2 += "meera"
$arrayList2 += "lisa"
$arrayList2 += "joseph"
And i want to display the output like this:
arrayList1
arrayList2
james
scott
henry
john
bob
meera
lisa
joseph
Here is what i tried:
$output = #{}
$output.add('arrayList1',$arrayList)
$output.add('arrayList2',$arrayLis2)
[PSCustomObject]$output
$output
And the output looks like this:
arrayList2
arrayList1
{scott,john,meera,lisa,joseph}
{james,henry,bob}
Note: The array won't have same number of data.
Any suggestions how i can get it in the order i want it?
Thanks
Sanjeev

A concise solution (note: assumes that at least one of the arrays is non-empty):
$arrayList1 = 'james', 'henry', 'bob'
$arrayList2 = 'scott', 'john', 'meera', 'lisa', 'joseph'
foreach ($i in 0..([Math]::Max($arrayList1.Count, $arrayList2.Count)-1)) {
[pscustomobject] #{ arrayList1 = $arrayList1[$i]; arrayList2 = $arrayList2[$i] }
}
The above yields the following, as desired:
arrayList1 arrayList2
---------- ----------
james scott
henry john
bob meera
lisa
joseph
As an aside: The - ultimately rejected - proposal in GitHub issue #14732 suggested enhancing the foreach statement in a way that would have made parallel enumeration more convenient, along the lines of
foreach ($elemFromList1, $elemFromList2 in $arrayList1, $arrayList2) { ... }

Try something like this:
$Boys = "Bob", "Noah", "Liam"
$Girls = "Olivia", "Sophia", "Charlotte", "Emma"
Function Fix-Arrays {
Param(
[Parameter(Mandatory=$True)]
[string[]]$ArrayNames,
[switch]$NoWarnings = $False
)
$ValidArrays,$ItemCounts = #(),#()
$VariableLookup = #{}
ForEach ($Array in $ArrayNames) {
Try {
$VariableData = Get-Variable -Name $Array -ErrorAction Stop
$VariableLookup[$Array] = $VariableData.Value
$ValidArrays += $Array
$ItemCounts += ($VariableData.Value | Measure).Count
}
Catch {
If (!$NoWarnings) {Write-Warning -Message "No variable found for [$Array]"}
}
}
$MaxItemCount = ($ItemCounts | Measure -Maximum).Maximum
$FinalArray = #()
For ($Inc = 0; $Inc -lt $MaxItemCount; $Inc++) {
$FinalObj = New-Object PsObject
ForEach ($Item in $ValidArrays) {
$FinalObj | Add-Member -MemberType NoteProperty -Name $Item -Value $VariableLookup[$Item][$Inc]
}
$FinalArray += $FinalObj
}
$FinalArray
}
Fix-Arrays -ArrayNames "Boys","Girls"
Just edit Fix-Arrays (line 41) and the top two example arrays to suit your needs.

Related

Find duplicate values in an array

i have the following code:
cls
Get-Module -ListAvailable | Import-Module
Import-Module ActiveDirectory
$Groups = Get-ADGroup -Filter {name -like 'XYZ'} | select name -
ExpandProperty name
$i=0
$tot = $Groups.count
$Table = #()
$Record = #{
"Group Name" = ""
"Name" = ""
"username" = ""
}
Foreach ($Group in $Groups) {
#// Set up progress bar
$i++
$status = "{0:N0}" -f ($i / $tot * 100)
Write-Progress -Activity "Exporting AD Groups" -status "Processing
Group $i of $tot : $status% Completed" -PercentComplete ($i / $tot *
100)
$Arrayofmembers = Get-ADGroupMember -identity $Group -recursive |
select name, SamAccountName
foreach ($Member in $Arrayofmembers) {
$Record."Group Name" = $Group
$Record."Name" = $Member.name
$Record."username" = $Member.SamAccountName
$objRecord = New-Object PSObject -property $Record
$Table += $objrecord
}
}
Write-Host $Table
which works perfectly but i want to list all duplicates in the $Record."Name" = $Member.name with specific group so for example:
username=barry is duplicated in GROUP XYZ
i tried already the following:
ForEach ($Element in $Table)
{
If (($Table -match $Element).count -gt 1)
{
"Duplicates detected"
}
}
The simplest answer is to just pipe $Table to Group-Object and filter for groups with a count greater than one at the end as such:
$Table | Group 'Group Name','Name' | Where{$_.Count -gt 1}
If you are looking to do this in the middle of your loop you could do so by grouping the results of Get-ADGroupMember, but I think it'll probably be faster to do it all at the end.
You could simply keep track of the members using a hashtable:
$seen = #{};
foreach ($Member in $Arrayofmembers)
{
if($seen[$Member.Name])
{
Write-Host "$($Member.Name) is duplicated in group $Group";
}
else
{
$seen.Add($Member.Name, $true);
}
# ... rest of the loop
}

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
}}
}

Output arrays to CSV

I have two CSVs:
Total 19_01_16.csv:
hostname,user,path,size,creation,LastAccess,Copied,NewName,Completed
comp1,user1,\\comp1\users1\file.pst,100,17/02/2015,17/01/2016,Yes,file_user1_.pst,
comp1,user1,\\comp1\users1\file2.pst,200,17/02/2015,17/01/2016,Yes,file2_user1_.pst,
comp2,user2,\\comp2\users2\file.pst,100,17/02/2015,17/01/2016,Yes,file_user2_.pst,
PST Passwords.csv:
user,Path,Password1,Password2,Password3,Error
user1,\\comp1\users1\file.pst,openme,openme,openme,
I'm trying to merge the two with different headers and additional content.
This is what I have so far:
$a = Import-Csv "$PST_PARENT\DailyReports\Total 19_01_16.csv"
"Hostname,User,PST_Name,Original_Path,New_Path,Size,AcceptableLoss,Password1,Password2,Password3" |
Set-Content "$PST_PARENT\DailyReports\New Build.csv"
$a | foreach {
$HOSTNAME = $_.hostname
$USER = $_.User
$PATH = $_.path
$NEW_NAME = $_.NewName
$NEWPATH = "$PST_SHARE\$USER\$NEW_NAME"
$SIZE = $_.Size
$SIZE_FAIL = ( [convert]::ToSingle( $SIZE ) / 2 )
$b = Import-Csv "$PST_PARENT\DailyReports\PST Passwords.csv"
$b | foreach {
if ( $USER -like $b.user ) {
$PASSWORD1 = $b.password1
$PASSWORD2 = $b.password2
$PASSWORD3 = $b.password3
} else {
$PASSWORD1 = "none"
$PASSWORD2 = "none"
$PASSWORD3 = "none"
}
}
$HOSTNAME,$USER,$NEW_NAME,$PATH,$NEWPATH,$SIZE,$SIZE_FAIL,$PASSWORD1,$PASSWORD2,$PASSWORD3 |
Add-Content "$PST_PARENT\DailyReports\New Build.csv"
}
The output of New Build.csv looks like this:
Hostname,User,PST_Name,Original_Path,New_Path,Size,AcceptableLoss,Password1,Password2,Password3
comp1
user1
file.pst
\\comp1\users1\file.pst
\\share\PST_Storage\file_user1_.pst
100
5
none
none
none
In essence the output is working, it's just not scrolling for each line, it's putting each array onto a new line.
I tried adding | ConvertTo-Csv -NoTypeInformation but all that did was convert the arrays to numbers, they still went down not across.
Any ideas? Am I on the right line or doing the whole thing so very wrong?
$HOSTNAME,$USER,... defines an array, which is written to the output file one element per line. You need to put the list in double quotes to turn it into a comma-separated string that you can write to the output file as a single line.
"Hostname,User,PST_Name,Original_Path,New_Path,Size,AcceptableLoss,Password1,Password2,Password3" |
Set-Content "$PST_PARENT\DailyReports\New Build.csv"
$a | foreach {
...
"$HOSTNAME,$USER,$NEW_NAME,$PATH,$NEWPATH,$SIZE,$SIZE_FAIL,$PASSWORD1,$PASSWORD2,$PASSWORD3"
} | Add-Content "$PST_PARENT\DailyReports\New Build.csv"
or you construct a custom object from the elements that you can export via Export-Csv.
$a | foreach {
...
New-Object -Type PSCustomObject -Property #{
'Hostname' = $HOSTNAME
'User' = $USER
'NewName' = $NEW_NAME
'Path' = $PATH
'NewPath' = $NEWPATH
'Size' = $SIZE
'SizeFail' = $SIZE_FAIL
'Password1' = $PASSWORD1
'Password2' = $PASSWORD2
'Password3' = $PASSWORD3
}
} | Export-Csv "$PST_PARENT\DailyReports\New Build.csv" -NoType

Easy way to List info from arrays

I have the code below which checks the registry for entries (more than 20 of them) and if it doesn't exists it creates a registry key and adds it to an array.
After that I need to check for all the names in the array to my other array and if it matches, I need it to pull the info from my second array and show it on the screen(the log location, registry location etc). But Can't really figure out how to match the array and write in on the screen without writing very long if statements.
Does anyone know a good way of doing this?
Thanks in advance!
$Reg = "HKLM:\Software\"
$NeedtoCheck = #()
$testing = #("Test1Name","Test2Name", "Test3Name")
$allTests = #(
$Test1 = #{
Name = "Test1"
Logfile = "C:\Checking\test1.log"
Version = "16"
RegName = "test1Nameinfo*"
Installname = "InstallTest1"
UninstallName = "UninstallTest1"
},
$Test2 = #{
Name = "Test"
Logfile = "C:\test2.log"
Version = "7"
RegName = "test2Nameinfo*"
Installname = "InstallTest2"
UninstallName = "UninstallTest2"
},
$Test3 = #{
Name = "Test3"
Logfile = "C:\Temp\Checkhere\test3.log"
Version = "99"
RegName = "test3Nameinfo*"
Installname = "InstallTest3"
UninstallName = "UninstallTest3"
}
$Test1Name = $Test1.name
$Test1Logfile = $Test1.Logfile
$Test1Version = $Test1.Version
$Test1RegName = $Test1.RegName
$Test1Install = $Test1.InstallName
$Test1Uninstall = $Test1.UninstallName
$Test2Name = $Test2.name
$Test2Logfile = $Test2.Logfile
$Test2Version = $Test2.Version
$Test2RegName = $Test2.RegName
$Test2Install = $Test2.InstallName
$Test2Uninstall = $Test2.UninstallName
$Test3Name = $Test3.name
$Test3Logfile = $Test3.Logfile
$Test3Version = $Test3.Version
$Test3RegName = $Test3.RegName
$Test3Install = $Test3.InstallName
$Test3Uninstall = $Test3.UninstallName
Foreach($Test in $testing){
$Key = (Get-Item "Reg").getvalue("$Test")
IF($Key -eq $null)
{
New-Itemproperty -path "HKLM:\Software\" -value "Check" -PropertyType string -name $Test -Force -ErrorAction SilentlyContinue
Write-Host "$Test created"
$Needtocheck += $Test
}
ELSEIF($key -eq "Check")
{
$Needtocheck += $Test
}
ELSE
{
Write-Host "$Test already Checked"
}
}
Foreach($item in $NeedtoCheck)
{
If($item -match $Test1Name)
{
Write-Host "$Test1Name info"
Write-host "$Test1Name`
$Test1Logfile`
$Test1Version`
$Test1RegName`
$Test1Install`
$Test1Uninstall`
}
Else
{
Write-Host "Not in the list"
}
}
....
This code doesn't make a lot of sense to be honest. If you want 20 checks to be setup, and then only run certain checks, then that's fine, but you really don't need additional cross checking to reference one array against another array, and redefining things like you do when you assign variables for each values in each hashtable. Personally I'd make objects not hashtables, but that's me. Actually, probably even better, make a hashtable with all available tests, then for the value make an object with the properties that you need. Oh, yeah, that'd be the way to go, but would need a little re-writing. Check this out...
$Reg = 'HKLM:\Software\'
$NeedtoCheck = #()
$testing = #('Test2','Test1','NotATest')
#Define Tests
$AllTests = #{'Test1' = [PSCustomObject]#{
Name = "Test1"
Logfile = "C:\Checking\test1.log"
Version = "16"
RegName = "test1Nameinfo*"
Installname = "InstallTest1"
UninstallName = "UninstallTest1"
}
'Test2' = [PSCustomObject]#{
Name = "Test"
Logfile = "C:\test2.log"
Version = "7"
RegName = "test2Nameinfo*"
Installname = "InstallTest2"
UninstallName = "UninstallTest2"
}
'Test3' = [PSCustomObject]#{
Name = "Test3"
Logfile = "C:\Temp\Checkhere\test3.log"
Version = "99"
RegName = "test3Nameinfo*"
Installname = "InstallTest3"
UnnstallName = "UninstallTest3"
}
}
#$allTests = #($Test1,$Test2,$Test3)
Foreach($Test in $Testing){
If($Test -in $allTests.Keys){
$Key = (Get-Item $Reg).getvalue($AllTests[$Test].RegName)
Switch($Key){
#Case - Key not there
{[string]::IsNullOrEmpty($_)}{
New-Itemproperty -path "HKLM:\Software\" -value "Check" -PropertyType string -name $AllTests[$Test].RegName -Force -ErrorAction SilentlyContinue
Write-Host "`n$Test created"
Write-Host "`n$Test info:"
Write-host $allTests[$test].Name
Write-host $allTests[$test].LogFile
Write-host $allTests[$test].Version
Write-host $allTests[$test].RegName
Write-host $allTests[$test].Installname
Write-host $allTests[$test].Uninstallname
}
#Case - Key = 'Check'
{$_ -eq "Check"}{
Write-Host "`n$Test info:`n"
Write-host $allTests[$test].Name
Write-host $allTests[$test].LogFile
Write-host $allTests[$test].Version
Write-host $allTests[$test].RegName
Write-host $allTests[$test].Installname
Write-host $allTests[$test].Uninstallname
}
#Default - Key exists and does not need to be checked
default {
Write-Host "`n$Test already Checked"
}
}
}Else{
Write-Host "`n$Test not in list"
}
}
That should do what you were doing before, with built in responses and checks. Plus this doesn't duplicate efforts and what not. Plus it allows you to name tests whatever you want, and have all the properties you had before associated with that name. Alternatively you could add a member to each test run, like 'Status', and set that to Created, Check, or Valid, then you could filter $AllTests later and look for entries with a Status property, and filter against that if you needed additional reporting.
You can filter down the tests you want to check like so, if I understand what you are asking for:
$Needtocheck | Where {$_ -in $testing} |
Foreach {... do something for NeedToCheck tests that existing in $testing ... }
I had to change several pieces of the code as there were syntax errors. Guessing most were from trying to create some sample code for us to play with. I have many comments in the code but I will explain some as well outside of that.
$Reg = "HKLM:\Software\"
$testing = "Test1","Test2", "Test3"
$allTests = #(
New-Object -TypeName PSCustomObject -Property #{
Name = "Test1"
Logfile = "C:\Checking\test1.log"
Version = "16"
RegName = "test1Nameinfo*"
Installname = "InstallTest1"
UninstallName = "UninstallTest1"
}
New-Object -TypeName PSCustomObject -Property #{
Name = "Test2"
Logfile = "C:\test2.log"
Version = "7"
RegName = "test2Nameinfo*"
Installname = "InstallTest2"
UninstallName = "UninstallTest2"
}
New-Object -TypeName PSCustomObject -Property #{
Name = "Test3"
Logfile = "C:\Temp\Checkhere\test3.log"
Version = "99"
RegName = "test3Nameinfo*"
Installname = "InstallTest3"
UninstallName = "UninstallTest3"
}
)
$passed = $testing | ForEach-Object{
# Changed the for construct to better allow output. Added the next line to make the rest of the code the same.
$test = $_
$Key = (Get-Item $Reg).getvalue($Test)
If($Key -eq $null){
# New-Itemproperty creates output. Cast that to void to keep it out of $passed
[void](New-ItemProperty -path "HKLM:\Software\" -value "Check" -PropertyType string -name $Test -Force -ErrorAction SilentlyContinue)
Write-Host "$Test created"
# Send this test to output
Write-Output $Test
} Elseif ($key -eq "Check")
{
# Send this test to output
Write-Output $Test
} Else {
Write-Host "$Test already Checked"
}
}
$allTests | Where-Object{$passed -contains $_.Name}
We run all the values in $testing and if one is created or already "Checked" then we send it down the pipe where it populates the variable $passed. The we take $allTests and filter out every test that has a match.

Get all combinations of an array

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
}

Resources