Paired enumeration of two arrays [duplicate] - arrays

This question already has answers here:
Powershell foreach regarding multiple collections
(1 answer)
Display multiple array values in list in Powershell
(2 answers)
Closed 3 months ago.
I have 2 arrays: I need to sort them in foreach to modify 1 file.
1 array with dbs name like:
$dbs = ['Honda', 'Toyota', 'BMW', 'Opel']
2nd array with their size like:
$dbsSize = ['123', '300', '222', '143']
and I can sort names in foreach like
foreach ($db in $dbs){
# some logic..
}
How can I sort 2nd array in the same foreach so I can use it in the same loop
Like this:
foreach ($db in $dbs) {
# some logic with $db
# and here I need some logic with $dbsSize
}
but to sort out $dbsSize like $dbs because I need the result like :
Honda
123
Toyota
300
etc.
Do I need to create another loop?

It seems to me you have two arrays where the indices determine which element of the one array belongs to the element in the other array.
It is unclear how you created them and why you did not create an array of objects with both properties together in the first place, but you can still do that like this:
$dbs = 'Honda', 'Toyota', 'BMW', 'Opel'
$dbsSize = '123', '300', '222', '143'
$result = for($i = 0; $i -lt [math]::Max($dbs.Count, $dbsSize.Count); $i++) {
[PsCustomObject]#{
Car = $dbs[$i]
Size = $dbsSize[$i]
}
}
# sort the items on 'Car' for instance
$result = $result | Sort-Object Car
# show on screen
$result
# save to structured CSV file you can open in Excel
$result | Export-Csv -Path 'X:\Somewhere\cars.csv' -UseCulture -NoTypeInformation
Result on screen using the above exampe looks like
Car Size
--- ----
BMW 222
Honda 123
Opel 143
Toyota 300

I don't see any sorting in your code, I only see enumeration. Seems like you want to enumerate both collections sequentially, in that case you would need to use a for loop instead of foreach:
$dbs = "['Honda', 'Toyota', 'BMW', 'Opel']" | ConvertFrom-Json
$dbsSize = "['123', '300', '222', '143']" | ConvertFrom-Json
for($i = 0; $i -lt [math]::Max($dbs.Count, $dbsSize.Count); $i++) {
$dbs[$i]
$dbsSize[$i]
}
If you want to use a foreach loop and both collections have the same Length, then you can get the items of one of them (your choice which) via indexing [ ]:
$i = 0
foreach($item in $dbs) {
$item
$dbsSize[$i++]
}
If you're looking to merge both arrays into objects, an easy way to do it is with the function from this answer, the usage in this case would be:
$dbs, $dbsSize | Join-Array -Columns Car, Size

Related

Different length arrays to one CSV

If you have multiple arrays of different length, how can you export these to a single csv in powershell?
Array1 = 1,2,3
Array2 = Bob,smithy,Alex,Jeremy
Array3 = yes,no
Output CSV
Number Name Valid
———————————————————
1 Bob Yes
2 Smithy no
3 Alex
Jeremy
Basically each array would be in its own header column.
Tried lines like
Array1 | Select-Object Number | export-csv -Path C:\Path
This works for singular arrays to singular csv files
But if I try
Array1, Array2, Array3 | Select-Object Number, Name, Valid | export-csv -Path C:\Path
I just get the header names and no values in the columns
One way to do it is with a for loop.
$Array1 = 1, 2, 3
$Array2 = 'Joe Bloggs', 'John Doe', 'Jane Doe'
$Array3 = 'Yes', 'No'
$export = for($i = 0; $i -lt [Linq.Enumerable]::Max([int[]] ($Array1.Count, $Array2.Count, $Array3.Count)); $i++) {
[pscustomobject]#{
Number = $Array1[$i]
Name = $Array2[$i]
Valid = $Array3[$i]
}
}
$export | Export-Csv path\to\csv.csv -NoTypeInformation
Another example using a function, the logic is more or less the same except that there is more overhead involved, since this function can handle an indefinite amount of arrays coming from the pipeline.
function Join-Array {
[CmdletBinding()]
param(
[parameter(ValueFromPipeline, Mandatory)]
[object[]] $InputObject,
[parameter(Mandatory)]
[string[]] $Columns
)
begin {
$inputDict = [ordered]#{}
$index = 0
}
process {
try {
if($MyInvocation.ExpectingInput) {
return $inputDict.Add($Columns[$index++], $InputObject)
}
foreach($item in $InputObject) {
$inputDict.Add($Columns[$index++], $item)
}
}
catch {
if($_.Exception.InnerException -is [ArgumentNullException]) {
$errorRecord = [Management.Automation.ErrorRecord]::new(
[Exception] 'Different count between input arrays and Columns.',
'InputArrayLengthMismatch',
[Management.Automation.ErrorCategory]::InvalidOperation,
$InputObject
)
$PSCmdlet.ThrowTerminatingError($errorRecord)
}
$PSCmdlet.ThrowTerminatingError($_)
}
}
end {
foreach($pair in $inputDict.GetEnumerator()) {
$count = $pair.Value.Count
if($count -gt $max) {
$max = $count
}
}
for($i = 0; $i -lt $max; $i++) {
$out = [ordered]#{}
foreach($column in $inputDict.PSBase.Keys) {
$out[$column] = $inputDict[$column][$i]
}
[pscustomobject] $out
}
}
}
The usage would be pretty easy, the arrays can be passed through the pipeline:
$Array1 = 1, 2, 3
$Array2 = 'Joe Bloggs', 'John Doe', 'Jane Doe'
$Array3 = 'Yes', 'No'
$Array4 = 'hello', 'world', 123, 456
$Array1, $Array2, $Array3, $Array4 | Join-Array -Columns Number, Name, Valid, Test |
Export-Csv path\to\csv.csv -NoTypeInformation
Or via Named Parameter / Positional Binding:
Join-Array $Array1, $Array2, $Array3, $Array4 -Columns Number, Name, Valid, Test |
Export-Csv path\to\csv.csv -NoTypeInformation
For quiet some time I am maintaining a Join-Object script/Join-Object Module (see also: In Powershell, what's the best way to join two tables into one?). It's main purpose is joining tables (aka lists of objects) based on a related column (aka property) defined by the -on parameter using an user (aka scripter) friendly syntax which as much joining options as possible. This has lead to a few implicated features that could be used in your specific request:
If there is no relation defined (the -on parameter is omitted), it is assumed that you want to do a side-by-side join of the tables (aka lists)
If the tables (or lists) are not equal in size, a common (inner) join will end when either lists ends. A full join (e.g. FullJoin-Object alias FullJoin or a left - or right join) at the other hand, will join the complete respective lists
An array of scalars (e.g. strings rather then -custom- objects) is joined as being a list of objects where the (default) column (aka property) name is Value
If both sides contain a list a scalars, the output will be a [Collections.ObjectModel.Collection[psobject]] list containing each left - and right item
Join commands can be chained: ... |Join ... |Join ...
If the columns (aka properties) provided in the tables (aka lists) overlap, they will be merged in an array ( <property name> = <left property value>, <right property value>) by default, which items could also be assigned to a specific name (prefix) using the -Name parameter. This parameter can be used with every join commando in the chain.
All together this means that you might reach your requirement with the following command line:
$Array1 |FullJoin $Array2 |FullJoin $Array3 -Name Number, Name, Valid
Number Name Valid
------ ---- -----
1 Joe Bloggs Yes
2 John Doe No
3 Jane Doe
You need to restructure the data to pivot it into one array (presented here in pseudo-json):
[
{"1", "Bob", "Yes"},
{"2", "Smithy", "no"},
{"3", "Alex", ""},
{"", "Jeremy", ""}
]
Note how the missing fields still appear with empty placeholders.
Once you've pivoted your data this way, writing it to a CSV file is trivial, as are many other tasks (this is the better way to structure things in the first place).

Compare duplicates in an array. List values that are contained and not contained in array [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 1 year ago.
Improve this question
I have these arrays, I want to list unique values. If a value in $arr2 is in $arr1 I want to list that out but only ONE time. If a value in $arr2 is NOT in $arr1 I want to list that out as well. I would like to also add the values in $arr2 that are not contained in $arr1 to a seperate array.
This is written in powershell but if you have a solution/tips in any other language that is perfectly fine with me I will rewrite to powershell.
$arr1 = #(1,1,1,2,2,3,4,4,5,7)
$arr2= #(1,2,3,4,5,6,7)
for ($i = 0; $i -lt $arr2.length; $i++){
for( $j = $i+1; $j -lt $arr1.length; $j++ ){
if($arr2[$i] -eq $arr1[$j]){
Write-Host $arr2[$i]
}
}
}
You can use something like this to create an object which can be used for your need, however it will require further code from your side (i.e.: filtering) to get the results you're looking for.
$arr1 = #(1,1,1,2,2,3,4,4,5,7)
$arr2 = #(1,2,3,4,5,6,7)
$result = [System.Collections.Generic.List[pscustomobject]]::new()
foreach($i in $arr1 + $arr2)
{
if($i -in $result.Value)
{
continue
}
$z = [ordered]#{
Value = $i
Array1 = $false
Array2 = $false
}
if($i -in $arr1)
{
$z.Array1 = $true
}
if($i -in $arr2)
{
$z.Array2 = $true
}
$result.Add([pscustomobject]$z)
}
$valuesOnBothArrays, $valuesOnOneArray = $result.Where({
$_.Array1 -eq $true -and $_.Array2 -eq $true}, 'Split'
)
$valuesOnBothArrays will result in:
Value Array1 Array2
----- ------ ------
1 True True
2 True True
3 True True
4 True True
5 True True
7 True True
$valuesOnOneArray will result in:
Value Array1 Array2
----- ------ ------
6 False True
I suggest using the Compare-Object cmdlet in combination with the .Where() array method:
$arr1 = 1,1,1,2,2,3,4,4,5,7
$arr2 = 1,2,3,4,5,6,7
$inBoth, $uniqueRight =
(Compare-Object -PassThru -IncludeEqual `
($arr1 | Select-Object -Unique) ($arr2 | Select-Object -Unique)).
Where({ $_.SideIndicator -in '==', '=>' }).
Where({ $_.SideIndicator -eq '==' }, 'Split')
"-- in both:"
$inBoth
"-- unique to arr2"
$uniqueRight
Note: Thanks to using Select-Object -Unique on the input arrays so as to only operate on distinct elements, the use of -PassThru with Compare-Object works as expected and passes the integers of interest through directly, rather than as properties of wrapper objects. The caveat is that with [int] array elements specifically, because PowerShell caches values up to 100, having duplicates in either collection would malfunction obscurely. The reason is that -PassThru decorates the pass-through elements with an ETS .SideIndicator property, which affects all uses of a given integer between 0 and 100, so that the .SideIndicator property value of a later duplicate would overwrite the original value - see this answer for more information.
Note:
If you know that the distinct elements of $arr1 are only ever a subset of the ones in $arr2, i.e. that $arr1 contains no unique elements, you can eliminate the intermediate .Where({ $_.SideIndicator -in '==', '=>' }) call.
Unfortunately, as of PowerShell 7.2, the implementation of Select-Object -Unique is quite inefficient - see GitHub issue #11221. If the input arrays were sorted, Get-Unique would be a faster alternative.
The above yields:
-- in both (distinct):
1
2
3
4
5
7
-- unique to arr2
6

How to construct an array of arrays with Powershell? [duplicate]

This question already has answers here:
Powershell Multidimensional Arrays
(11 answers)
Closed 3 years ago.
To group data, every time there's a "new person" as below, I want to add their info to that temporary array and reset that array to null.
Before each "new person" array is set to null, I want to add that array to an array of people. An array of arrays.
How can I add one array into another?
$people = import-csv "./people.csv"
$h = #{}
$h.gettype()
$all_people
ForEach ($person in $people) {
$new_person
if ($person -match '[0-9]') {
Write-host $person
}
else {
write-host "new person"
write-host $person
}
}
output:
thufir#dur:~/flwor/people$
thufir#dur:~/flwor/people$ pwsh foo.ps1
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Hashtable System.Object
new person
#{people=joe}
#{people=phone1}
#{people=phone2}
#{people=phone3}
new person
#{people=sue}
#{people=cell4}
#{people=home5}
new person
#{people=alice}
#{people=atrib6}
#{people=x7}
#{people=y9}
#{people=z10}
thufir#dur:~/flwor/people$
I have something like this:
$people = import-csv "./people.csv"
$all_people
$new_person = "new","person"
$new_person.GetType()
ForEach ($person in $people) {
if ($person -match '[0-9]') {
Write-host $person
$new_person.Add($person)
}
else {
write-host "new person"
write-host $person
#$new_person = null
$new_person = "new","person"
}
}
Powershell doesnt provide a good functionality to create an array of array with use of basic arrays.
What you can do use use an array of hashtable or PsCustomObjects to create yourself an array of arrays.
Yes you can create array of arrays.
Example we create 3 arrays like
$a = 1..5
$b = 6..10
$c = 11..15
Now we can add them in another array $d
$d = $a,$b,$c
Now we can access them like:
$d[0]
# Output
# 1
# 2
# 3
# 4
# 5
$d[0][2]
# Output is $a arrays 3rd element

Format an array of hashtables as table with Powershell

I wish to display an array of hastables as a Table. I found several threads dealing with this issue, but so far no solution has worked for me (see here and here).
My array consists of 18 hashtables in this form:
Array = #(
#{Company=1}
#{Company=2}
#{Company=3}
#{Contact=X}
#{Contact=Y}
#{Contact=Y}
#{Country=A}
#{Country=B}
#{Country=C}
)
I would like to get the following output:
Company Contact Country
------------------------------
1 X A
2 Y B
3 Z C
I've tried the following:
$Array | ForEach-Object { New-Object -Type PSObject -Property $_ } | Format-Table
That displays the following:
Company
----------
1
2
3
A Format-List works better; I then get this:
Company: 1 2 3
Contact: X Y Z
Country: A B C
Is there any way to accomplish my desired output?
You can do the following if you want to work with CSV data and custom objects:
$Array = #(
#{Company=1}
#{Company=2}
#{Company=3}
#{Contact='X'}
#{Contact='Y'}
#{Contact='Z'}
#{Country='A'}
#{Country='B'}
#{Country='C'}
)
$keys = $Array.Keys | Get-Unique
$data = for ($i = 0; $i -lt $Array.($Keys[0]).Count; $i++) {
($Keys | Foreach-Object { $Array.$_[$i] }) -join ','
}
($Keys -join ','),$data | ConvertFrom-Csv | Format-Table
Explanation:
Since $Array is an array of hash tables, then the .Keys property will return all the keys of those hash tables. Since we only care about unique keys when building our object, Get-Unique is used to remove duplicates.
$Array.($Keys[0]).Count counts the number of items in a group. $Keys[0] here will be Company. So it returns the number of hash tables (3) that contain the key Company.
$Array.Company for example returns all values from the hash tables that contain the key Company. The Foreach-Object loops through each unique key's value at a particular index ($i). Once each key-value is read at a particular index, the values are joined by a comma.
When the loop completes, ($Keys -join ','),$data outputs the data in CSV format, which is piped into ConvertFrom-Csv to create a custom object.
Note: that if your data contains commas, you may want to consider the alternative method below.
Alernatively, you can work with hash tables and custom objects with the following:
$Array = #(
#{Company=1}
#{Company=2}
#{Company=3}
#{Contact='X'}
#{Contact='Y'}
#{Contact='Z'}
#{Country='A'}
#{Country='B'}
#{Country='C'}
)
$keys = $Array.Keys | Get-Unique
$data = for ($i = 0; $i -lt $Array.($Keys[0]).Count; $i++) {
$hash = [ordered]#{}
$Keys | Foreach-Object { $hash.Add($_,$Array.$_[$i]) }
[pscustomobject]$hash
}
$data | Format-Table

Convert a onedimensional Array to a multidimensional Array

I have an array in the following format:
C123456,
John Example,
C654321,
Mike Lastname,
C999999,
Elisabeth Average
Is there an easy way which I can convert that array to something like this:
CPName Name
C123456 John Example
C654321 Mike Lastname
C999999 Elisabeth Average
Iterate over the array with step size 2 and build custom objects from all even indexes and their next neighbor:
$list = for ($i=0; $i -lt $arr.Count; $i+=2) {
New-Object -Type PSObject -Property #{
'CPName' = $arr[$i].TrimEnd(',')
'Name' = $arr[$i+1].TrimEnd(',')
}
}

Resources