Why is Powershell Flattening my Array? - powershell

$arrays = #();
$array = #();
$string = 'one|two|three';
$array = ($string -split '\|');
$arrays += $array;
$arrays[0][0]
I expected $arrays to be a two dimensional array the first element of which would be a reference to $array. Thus, $arrays[0][0] would contain the string 'one'.
Instead PoSh seems to be flattening $arrays into a single list containing the elements 'one', 'two', and 'three'. This is true even if I put the #() array constructor around $array in my append operation.
Adding an additional element to my append gets me close, but then I'll have empty elements in $arrays:
$arrays = #();
$array = #();
$string = 'one|two|three';
$array = ($string -split '\|');
$arrays += $array, '';
$arrays[0][0]

Think of PowerShell's + operator with an array as the LHS as concatenating arrays, i.e., appending the individual elements of the RHS to the LHS array[1]
:
# Note: , has higher precedence than +, so this is the same as: ((1, 2) + (3, 4)).Count
> (1, 2 + 3, 4).Count # Same as: (1, 2, 3, 4).Count
4
even if I put the #() array constructor around $array
It is , that is the array-construction operator.
#() is the array sub-expression operator - its purpose is to ensure that the output from the enclosed command becomes an array unless it already is one.
In other words: Something like #(1, 2) is a no-op, because 1, 2 already is an array.
Therefore, as you've discovered, using , to construct a nested array is the solution:
> (1, 2 + , (3, 4)).Count
3
, (3, 4) wraps array 3, 4 in a single-element array.
+ then adds each element of that array - the one and only element that is the wrapped 3, 4 array - to the LHS.
[1] Let's not forget that a .NET array is an immutable data structure,
so what is really happening is that, behind the scenes, PowerShell constructs a new array.

After some additional research I found this post. This is, apparently, done by design. Adding a comma prior to $array in my append operation fixes the problem:
$arrays = #();
$array = #();
$string = 'one|two|three';
$array = ($string -split '\|');
$arrays += ,$array;
$arrays[0][0]

Related

Powershell - How to Transpose an array

I have an array with data separated by a comma. I need to transpose it so the first part before the comma for each line is joined together by a delimiter as one line and the same for a second part. Example:
AC-2.22,CCI-000012
AC-5.1,CCI-000036
AC-5.3,CCI-001380
I want to have 2 separate variables like so:
variable 1 = AC-2.22; AC-5.1; AC-5.3
Variable 2 = CCI-000012; CCI-000036; CCI-001380
I know this should be simple but I've been staring at code all day and I just want to go eat dinner and goto sleep.
Thanks in advance
Based on the $array of the helpful answer from Santiago Squarzon, you might also use the ConvertFrom-Csv cmdlet to transpose the data using member-access enumeration:
$data = ConvertFrom-Csv $Array -Header var1, var2
$data.var1 -Join ';'
AC-2.22;AC-5.1;AC-5.3
$data.var2 -Join ';'
CCI-000012;CCI-000036;CCI-001380
This is not too hard using .Where method with Split mode:
$array = #(
'AC-2.22,CCI-000012'
'AC-5.1,CCI-000036'
'AC-5.3,CCI-001380'
)
$i = $false
$var1, $var2 = $array.Split(',').Where({ ($i = -not $i) }, 'Split')
$var1 -join '; '
$var2 -join '; '
.Split method works in this case thanks to Member-Access Enumeration, because arrays don't have an .Split method the same is called over each element of the array:
When you use the member-access operator on any object and the specified member exists on that object, the member is invoked. For property members, the operator returns the value of that property. For method members, the operator calls that method on the object.
When you use the member-access operator on a list collection object that doesn't have the specified member, PowerShell automatically enumerates the items in that collection and uses the member-access operator on each enumerated item.

How to compare values in arrays and add the value from one array to another

I've got 2 arrays:
$array1 = ('1','2','7','9')
$array2 = ('7','9','1','2')
These collections are dynamically changed and I need to add the values from the first array to second. I need to create a condition if.... and check... if these arrays are equals - do nothing.
But, if the first array has new value for example it becomes $array1 = ('1','2','7','9', '6') and second array doesn't have such value
then I need to add this '6' new value to the second array...so the second array will become $array2 = ('7','9','1','2', '6')... please help to achieve it.
Please note that values in 2 arrays could be in different order it doesn't matter, the goal is to have the same values inside 2 arrays.
Merge the two and sort out non-unique values:
$array2 = #($array1;$array2) |Sort-Object -Unique
For large arrays you may want to add all items from both arrays to a HashSet instead - it'll only store distinct values, so the resulting set will correspond to the new value of $array2:
$set = [System.Collections.Generic.HashSet[psobject]]::new()
$array1 |% { [void]$set.Add($_) }
$array2 |% { [void]$set.Add($_) }
$array2 = #($set)
If you want to retain the order in $array2 and append those elements (in order) from $array1 that aren't yet in $array2, you can use Compare-Object:
$array1 = '1','2','7','9','6'
$array2 = '7','9','1','2'
$array2 +=
(Compare-Object -PassThru $array1 $array2 | Where-Object SideIndicator -eq '<=')
Note: Compare-Object, like PowerShell in general, is case-insensitive by default; add -CaseSensitive if needed.
You can use IsSupersetOf and UnionWith from HashSet<T>:
$array1 = [string[]] ('1','2','7','9','new value')
$array2 = [System.Collections.Generic.HashSet[string]] ('7','9','1','2')
if(-not $array2.IsSupersetOf($array1)) {
$array2.UnionWith($array1)
}
$array2
In case you need to have an actual array back you can cast [object[]] or [string[]] back depending on your need:
[string[]] $array2
Worth mentioning that hashsets are case sensitive by default, in case you need a case-insensitive hashset you can instantiate it using a case-insensitive comparer:
$array2 = [System.Collections.Generic.HashSet[string]]::new(
[string[]]('7','9','1','2'),
[System.StringComparer]::OrdinalIgnoreCase
)
You could build on top of this solution, by creating a function.
This code block lists array 1 and array 2
It uses the -notcontains cmdlet to check if the elements in array are in array2 and if not, then appends the element in array 2.
$array = #('1','2','7','9', '15')
$array2 = #('1','2','7','9')
Write-Output "$array `n"
Write-Output "$array2 `n"
$array | ForEach-Object {
if ($array2 -notcontains $_) {
$array2 += $_
}
}
Write-Output "$array2"
Hope this helps!

powershell script to make array elements as the keys of hashtable

I want to assign all the array elements as the keys of hash table. For Example...
# arrays
$k=#(1,2,3)
$v=#("one","two","three")
# hashtable
$table=#{}
I want output like this:
$table={1="one",2="two",3="three"}
Is there any way to do it?
0..($k.count-1) |
Foreach-Object -Begin {
$table=[ordered]#{}
} -process {
$table.Add($k[$_],$v[$_])
}
Assuming each array is the same size, you can loop through your indexes and grab elements at the same index from both arrays. The Add(key,value) method adds new key-value pairs to your hash table.
You can also accomplish this with a for each loop to be able to easily track the index between both arrays
$k = #(1, 2, 3)
$v = #("one", "two", "three")
$table = #{}
for ($i=0; $i -lt $k.Length; $i++){
$table[$k[$i]] = $v[$i]
}
Note: this is also somtimes referred to as "zipping" two arrays

Powershell- How to identify via variable, the matched string index on a array

$array = #('blue','red','purple','pink')
$array2 = #('brown','red','black','yellow')
$array | ForEach-Object {
if ($array2 -contains $_) {
Write-Host "`$array2 contains the `$array1 string [$_]"
}
}
how to get the index of the match string?
While PowerShell's -in / -contains operators allow you to test for containment of a given value in a collection (whether a given value is an element of the collection), there is no direct support for getting an element's index using only PowerShell's own features.
For .NET arrays (such as the ones created in your question[1]) you can use their .IndexOf() instance method, which uses case-SENSITIVE comparison based on the current culture; e.g.:
$array.IndexOf('red') # -> 1; case-SENSITIVE, current-culture comparison
Note that PowerShell itself is generally case-INSENSITIVE, and with -eq (and in other contexts) uses the invariant culture for comparison.
A case-INSENSITIVE solution based on the invariant culture, using the Array type's static [Array]::FindIndex() method:
$array = 'blue', 'ReD', 'yellow'
[Array]::FindIndex($array, [Predicate[string]] { 'red' -eq $args[0] }) # -> 1
Note that by delegating to a PowerShell script block ({ ... }) in which each element ($args[0]) is tested against the target value with -eq, you implicitly get PowerShell's case-insensitive, culture-invariant behavior.
Alternatively, you could use the -ceq operator for case-sensitive (but still culture-invariant) matching.
($args[0].Equals('red', 'CurrentCulture') would give you behavior equivalent to the .IndexOf() solution above).
Generally, this approach enables more sophisticated matching techniques, such as by using the regex-based -match operator, or the wildcard-based -like operator.
The above solutions find the index of the first matching element, if any.
To find the index of the last matching element, if any, use:
.LastIndexOf()
[Array]::FindLastIndex()
Note: While there is an [Array]::FindAll() method for returning all elements that meet a given predicate (criterion), there is no direct method for finding all indices.
[1] Note that you do not need #(), the array-subexpression operator to create an array from individually enumerated elements: enumerating them with ,, the array constructor operator alone is enough:
$array = 'blue','red','purple','pink'
Looks like a homework exercise to me. In any case, as mentioned, things are a lot easier if you format your code properly. It's also easier if you name your variables rather than relying on $_, because it changes as it goes through a nested loop.
There are also other ways to do this - do you want the index number or the contents? I assumed the latter
$array = #('blue','red','purple','pink')
$array2 = #('brown','red','black','yellow')
ForEach ($a in $array) {
if ($array2 -contains $a) {
Write-Host "`$array2 contains the `$array1 string $a"
}
}
$array2 contains the $array1 string red
You can try something with an index counter you can use. If $array2.ToLower() contains that element.ToLower(), then loop through that second array to find out where that element actually is.
Note that this is not going to work for large amount of arrays as the time it will take to go through would get larger and larger. But, for small samples like this one, it works fine.
$array = 'blue','Red','purple','pink', 'browN'
$array2 = 'brown','rEd','black','yellow'
$array | ForEach-Object {
if ($array2.ToLower() -contains $_.ToLower()) {
$index = 0
foreach($arrElement in $array2) {
#$index++ # based on index starting with 1
if ($arrElement -eq $_) {
Write-Host "`$array2 contains the `$array1 string [$_] at index: $index"
}
$index++ # based on index starting with 0
}
}
}
# produces output
$array2 contains the $array1 string [Red] at index: 1
$array2 contains the $array1 string [browN] at index: 0
If there are duplicates in the $array2, you'll get two separate lines that would show each index entry.
$array = 'blue','Red','purple','pink', 'browN'
$array2 = 'brown','rEd','black','yellow', 'red'
#Output would be with above code:
$array2 contains the $array1 string [Red] at index: 1
$array2 contains the $array1 string [Red] at index: 4
$array2 contains the $array1 string [browN] at index: 0
You could also do a for loop using an index counter:
$array = 'blue','red','purple','pink', 'black'
$array2 = 'brown','red','black','yellow', 'red'
for ($i = 0; $i -lt $array2.Count; $i++) {
if ($array -contains $array2[$i]) {
Write-Host "`$array2 contains the the string '$($array2[$i])' at index: $i"
}
}
Result:
$array2 contains the the string 'red' at index: 1
$array2 contains the the string 'black' at index: 2
$array2 contains the the string 'red' at index: 4
This is a practical example that uses BinarySearch and relies on your look-up array being sorted by the property of "interest".
Uses IComparer to force case insensitivity
# BinarySearch needs a sorted array
$mySortedArray = Get-ChildItem $env:TEMP | Sort-Object -Property Name
# Provide files available on your machine
$anotherArray = #(
'mat-debug-23484.log'
'MSIaa547.LOG'.ToLower()
)
foreach ($item in $anotherArray) {
$index = $null
# BinarySearch defaults to being case sensitive
$index = [array]::BinarySearch($mySortedArray.Name, $item,[Collections.CaseInsensitiveComparer]::Default)
# If no matches found index will be negative
if ($index -ge 0) {
Write-Host ('Index {0} filename {1} found!' -f $index, $mySortedArray[$index].Name) -ForegroundColor Green
}
}
# Adjusted to meet your example code
$array = #('blue','red','purple','pink')
$array2 = #('brown','red','black','yellow') | Sort-Object
$array | ForEach-Object {
$currentObject = $_
$index = $null
$index = [array]::BinarySearch($array2, $currentObject, [System.Collections.CaseInsensitiveComparer]::Default)
if ($index -ge 0) {
Write-Host ('Index={0} array2 value="{1}" found!' -f $index, $array2[$index]) -ForegroundColor Green
}
}

Powershell array of arrays [duplicate]

This question already has answers here:
Powershell create array of arrays
(3 answers)
Closed 5 years ago.
This is building $ret into a long 1 dimensional array rather than an array of arrays. I need it to be an array that is populated with $subret objects. Thanks.
$ret = #()
foreach ($item in $items){
$subret = #()
$subRet = $item.Name , $item.Value
$ret += $subret
}
there might be other ways but arraylist normally works for me, in this case I would do:
$ret = New-Object System.Collections.ArrayList
and then
$ret.add($subret)
The suspected preexisting duplicate question is indeed a duplicate:
Given that + with an array as the LHS concatenates arrays, you must nest the RHS with the unary form of , (the array-construction operator) if it is an array that should be added as a single element:
# Sample input
$items = [pscustomobject] #{ Name = 'n1'; Value = 'v1'},
[pscustomobject] #{ Name = 'n2'; Value = 'v2'}
$ret = #() # create an empty *array*
foreach ($item in $items) {
$subret = $item.Name, $item.Value # use of "," implicitly creates an array
$ret += , $subret # unary "," creates a 1-item array
}
# Show result
$ret.Count; '---'; $ret[0]; '---'; $ret[1]
This yields:
2
---
n1
v1
---
n2
v2
The reason the use of [System.Collections.ArrayList] with its .Add() method worked too - a method that is generally preferable when building large arrays - is that .Add() only accepts a single object as the item to add, irrespective of whether that object is a scalar or an array:
# Sample input
$items = [pscustomobject] #{ Name = 'n1'; Value = 'v1'},
[pscustomobject] #{ Name = 'n2'; Value = 'v2'}
$ret = New-Object System.Collections.ArrayList # create an *array list*
foreach ($item in $items) {
$subret = $item.Name, $item.Value
# .Add() appends whatever object you pass it - even an array - as a *single* element.
# Note the need for $null = to suppress output of .Add()'s return value.
$null = $ret.Add($subret)
}
# Produce sample output
$ret.Count; '---'; $ret[0]; '---'; $ret[1]
The output is the same as above.
Edit
It is more convoluted to create an array of tuples than fill an array with PsObjects containing Name Value as the two properties.
Select the properties you want from $item then add them to the array
$item = $item | select Name, Value
$arr = #()
$arr += $item
You can reference the values in this array by doing this
foreach($obj in $arr)
{
$name = $obj.Name
$value = $obj.Value
# Do actions with the values
}