powershell sort & subsort multidimensional array - arrays

I'm new at PowerShell. I'm working on how to do a sort & sub-sort of a multidimensional array. I have an array of addresses:
$Addresses += ,#($FirstName, $LastName, $Street, $City, $State, $ZIP);
I need to use the array in two ways. First sorted by last name then first name. Then later I need it sorted by state then city then last name.
I've figured out a one-level sort by last name:
$Addresses = $Addresses | sort-object #{Expression={$_[1]}; Ascending=$TRUE}
But I can't figure out how to sub-sort the resulting array by first name.
Any suggestions?
Thanks
UPDATE: Ok, I found the solution. I was so close. To sort by last name then first, use the expression:
$Addresses = $Addresses | sort-object #{Expression={$_[1],$_[0]}; Ascending=$TRUE}
Simple once I wrapped my brain about Sort-Object.

Related

Powershell Compare 2 Arrays of Hashtables based on a property value

I have one array of hashtables like the one below:
$hashtable1 = #{}
$hashtable1.name = "aaa"
$hashtable1.surname =#()
$hashtable1.surname += "bbb"
$hashtable2 = #{}
$hashtable2.name = "aaa"
$hashtable2.surname =#()
$hashtable2.surname += "ccc"
$hashtable3 = #{}
$hashtable3.name = "bbb"
$hashtable3.surname = #()
$hashtable3.surname += "xxx"
$A = #($hashtable1; $hashtable2; $hashtable3)
I need to iterate though the array and I need to find out duplicates based on hashtable[].name
Then I need to group those hashtable.surname to hashtable[].surname so that the result will be an array of hashtables that will group all for name all the surnames:
$hashtable1.name = "aaa"
$hashtable1.surname = ("bbb","ccc")
$hashtable3.name = "bbb"
$hashtable3.surname = ("xxx")
I was looking into iterating to empty array
+
I have found this link:
powershell compare 2 arrays output if match
but I am not sure on how to reach into the elements of the hashtable.
My options:
I was wondering if -contain can do it.
I have read about compare-object but I am not sure it can be done like that.
(It looks a bit scary in the moment)
I am on PS5.
Thanks for your help,
Aster
You can group your array items by the names using a scriptblock like so.
Once grouped, you can easily build your output to do what you seek.
#In PS 7.0+ you can use Name directly but earlier version requires the use of the scriptblock when dealing with arrays of hashtables.
$Output = $A | Group-Object -Property {$_.Name} | % {
[PSCustomObject]#{
Name = $_.Name
Surname = $_.Group.Surname | Sort-Object -Unique
}
}
Here is the output variable content.
Name Surname
---- -------
aaa {bbb, ccc}
bbb xxx
Note
Improvements have been made in PS 7.0 that allows you to use simply the property name (eg: Name) in Group-Object for arrays of hashtables, just like you would do for any other arrays type. For earlier version though, these particular arrays must be accessed by passing the property in a scriptblock, like so: {$_.Name}
References
MSDN - Group_Object
SS64 - Group Object
Dr Scripto - Use a Script block to create custom groupings in PowerShell

In Powershell Why might a foreach-object loop not let you retrieve individual values from an array? [duplicate]

This question already has answers here:
PowerShell outputting array items when interpolating within double quotes
(3 answers)
Closed 2 years ago.
Note the following output.
$Names is an array with two values.
$Machinename is an array with two values.
Their positional values within the array are retrieved accurately outside of a foreach loop.
When fetched within a foreach loop the value requested for the first position in the array i.e. $Names[0] ignores the positional call of [0]..... I need to be able to retrieve that value by itself..... Ultimately I will need to interate through each value to input to a command...
PS C:\Users\lab> $Names
john
jeff
PS C:\Users\lab> $Names[0]
john
PS C:\Users\lab> $Names[1]
jeff
PS C:\Users\lab> $Machinename
dev1
dev2
PS C:\Users\htlab> $Machinename | ForEach-Object { Write-Output "$Names[0]"}
john jeff[0]
john jeff[0]
Sample Script:
$Names = 'john', 'jeff'
$machinename = 'dev1', 'dev2'
$Machinename | ForEach-Object {Write-Output "$Names[0]"}
You're not evaluating the array. By writing "$Names[0]" it is the equivalent of $Names + "[0]".
You need to nest the evaluation inside $(...).
Try this:
$Machinename | ForEach-Object {Write-Output "$($Names[0])"}
That gives me:
john
john
Equally, as pointed out in the comments, this works too:
$Machinename | ForEach-Object {Write-Output $Names[0]}
You could try something like this, as it seemed to work for me, unless thats not what you're asking,
for (($i = 0); $i -lt $names.Count; $i++)
{
$Names[$i] + ":" + $Machinename[$i]
}
Output:
John:dev1
Jeff:dev2

Convert results to an array

I've got this code:
$myarray = #(#(0..3) | foreach {(#(0..$_) | Measure-Object -Sum).Sum})
[array]::indexof($myarray,2)
$myarray contains the values 0,1,3,6 so I was hoping [array]::indexof($myarray,3) would return the value 2, but it does not. it returns -1.
I'm assuming that the thing I think is an array isn't actually an array, so I'm assuming the thing I'm assigning to $myarray needs to get converted to an array somehow, clearly I'm doing it wrong.
An easier way of getting values in an array by index number is like this:
$myarray[2]
Sorry, misunderstood your problem. To get the first occurrence of a value in the array, try this:
$myarray.IndexOf([double]3)
Figured it out, sorry. Its an array of doubles, not a array of ints. This works (and is actually what I wanted, i.e. the element contiaing 2016):
$array = #(0..64) | foreach {[int](#(0..$_) | Measure-Object -Sum).Sum}
[array]::indexof($array,2016)

Unique Combos from powershell array - No duplicate combos

I'm trying to figure out the best way to get unique combinations from a powershell array. For instance, my array might be
#(B,C,D,E)
I would be hoping for an output like this :
B
C
D
E
B,C
B,D
B,E
C,D
C,E
D,E
B,C,D
C,D,E
B,C,D,E
I do not want re-arranged combos. If combo C,D exists already then I do not want combo D,C. It's redundant for my purposes.
I looked into the functions here : Get all combinations of an array
But they aren't what I want. I've been working on figuring this out myself, but have spent quite a bit of time without success. I thought I'd ask the question here so that if someone else already know I'm not wasting my time.
Thanks!
This is an adaptation from a solution for a C# class I took that asked this same question. For any set find all subsets, including the empty set.
function Get-Subsets ($a){
#uncomment following to ensure only unique inputs are parsed
#e.g. 'B','C','D','E','E' would become 'B','C','D','E'
#$a = $a | Select-Object -Unique
#create an array to store output
$l = #()
#for any set of length n the maximum number of subsets is 2^n
for ($i = 0; $i -lt [Math]::Pow(2,$a.Length); $i++)
{
#temporary array to hold output
[string[]]$out = New-Object string[] $a.length
#iterate through each element
for ($j = 0; $j -lt $a.Length; $j++)
{
#start at the end of the array take elements, work your way towards the front
if (($i -band (1 -shl ($a.Length - $j - 1))) -ne 0)
{
#store the subset in a temp array
$out[$j] = $a[$j]
}
}
#stick subset into an array
$l += -join $out
}
#group the subsets by length, iterate through them and sort
$l | Group-Object -Property Length | %{$_.Group | sort}
}
Use like so:
PS C:>Get-Subsets #('b','c','d','e')
b
c
d
e
bc
bd
be
cd
ce
de
bcd
bce
bde
cde
bcde
Note that computational costs go up exponentially with the length of the input array.
Elements SecondstoComplete
15 46.3488228
14 13.4836299
13 3.6316713
12 1.2542701
11 0.4472637
10 0.1942997
9 0.0867832
My tired attempt at this. I did manage to get it to produce the expected results but how it does it is not as elegant. Uses a recursive functionality.
Function Get-Permutations{
Param(
$theInput
)
$theInput | ForEach-Object{
$element = $_
$sansElement = ($theInput | Where-Object{$_ -ne $element})
If($sansElement.Count -gt 1){
# Build a collection of permutations using the remaining elements that were not isolated in this pass.
# Use the single element since it is a valid permutation
$perms = ,$element
For($elementIndex = 0;$elementIndex -le ($sansElement.Count - 1);$elementIndex++){
$perms += ,#(,$element + $sansElement[0..$elementIndex] | sort-object)
}
# For loop does not send to output properly so that is the purpose of collecting the results of this pass in $perms
$perms
# If there are more than 2 elements in $sansElement then we need to be sure they are accounted for
If($sansElement -gt 2){Get-Permutations $sansElement}
}
}
}
Get-Permutations B,C,D,E | %{$_ -join ","} | Sort-Object -Unique
I hope I can explain myself clearly....So each pass of the function will take an array. Each individual element of that array will be isolated from the rest of the array which is represented by the variables $element and $sansElement.
Using those variables we build individual and progressively larger arrays composing of those elements. Let this example show using the array 1,2,3,4
1
1,2
1,2,3
1,2,3,4
The above is done for each "number"
2
2,1
2,1,3
2,1,3,4
and so forth. If the returned array contains more that two elements (1,2 would be the same as 2,1 in your example so we don't care about pairs beyond one match) we would take that array and run it through the same function.
The real issue is that the logic here (I know this might be hard to swallow) creates several duplicates. I suppose you could create a hashtable instead which I will explore but it does not remove the logic flaw.
Regardless of me beating myself up as long as you don't have thousands of elements the process would still produce results.
Get-Permutations would return and array of arrays. PowerShell would display that one element per line. You asked for comma delimited output which is where -join comes in. Sort-Object -Unique takes those sorted string an discards the duplicates.
Sample Output
B
B,C
B,C,D
B,C,D,E
B,C,E #< Missing from your example output.
B,D
B,D,E #< Missing from your example output.
B,E
C
C,D
C,D,E
C,E
D
E

create a new variable for each loop in a foreach-loop

How can I put $org into an array together with $count?
Like this example array:
$myArray = #{
1="SampleOrg";
2="AnotherSampleOrg"
}
Another example:
$myArray = #{
$count="$org";
$count="$org"
}
Example foreach:
$count=0;get-organization | foreach {$count++; $org = $_.Name.ToString();write-host $count -nonewline;write-host " $org"}
$answer = read-host "Select 1-$count"
The above will display:
1 SampleOrg
2 AnotherSampleOrg
Select 1-2:
What I would like to do afterwards is to put the array to use in a switch.
Example:
switch ($answer)
{
1 {$org=myArray[1]} #<-- or whatever that corresponds to "SampleOrg"
2 {$org=myArray[2]} #<-- or whatever that corresponds to "AnotherSampleOrg"
}
You have to initialize your hashtable somewhere before the loop:
$myArray = #{}
and add a
$myArray.Add($count, $org)
to your foreach-loop.
EDIT: For the discussion about hastable/array see the whole thread ;) I just kept the name of the variable from the original posting
Looks like you're confusing arrays and Hashtables. Arrays are ordered, and indexed by an numeric value. Hashtables are associative, and indexed by any value that has equality defined.
This is array syntax
$arr = #(1,2,3)
and this is Hashtable syntax
$ht = #{red=1;blue=2;}
For your question, the following will work
$orgs = #(get-organization | % { $_.Name })
this will create a 0 based array, mapping int -> OrgName, so
$orgs[$answer]
will get the correct name. Or if you're using 1 based indexing
$orgs[$answer-1]
Note, I removed the switch, as there's no reason for it.

Resources