Powershell array assignment assigns variable, not value? - arrays

I have an example of a program that creates an array, and then attempts to assign the value of that array multiple times into another array as a multidimensional array.
$a =#(0,0,0)
$b = #($a,$a,$a)
$b[1][2]=2
$b
'And $a is changed too:'
$a
The output is:
PS E:\Workarea> .\what.ps1
0
0
2
0
0
2
0
0
2
And $a is changed too:
0
0
2
So in this instance, the variable is actually pointing to the original variable. This is very unexpected behavior. It's rather neat that one can do this, although I never did use unions that much in my C programming. But I'd like a way to actually just do the assignment of the value, not of the variable.
$b = #($a.clone(),$a.clone(),$a.clone())
I guess would work, but something tells me that there may be something a little more elegant than that.
Thanks for the input.
This is PowerShell 2.0 under Windows 7 64-bit.

To assign the values of $a to $b instead of the reference to $a, you can wrap the variable in $(). Anything in $() gets evaluated before using it in the command, so $($a) is equivalent to 0, 0, 0.
$a =#(0,0,0)
$b = #($($a),$($a),$($a))
$b[1][2]=2
$b
'$a is not changed.'
$a

Be careful in PowerShell ',' is not the enumerator operator, but an array aoperator. The thing you present as multidimensional array is in fact an array of array, you'll find here under the definition of a multidimensional array :
$a= new-object ‘object[,]’ 3,3
$a[0,2]=3
PS > for ($i=0;$i -lt 3;$i++)
>> {
>> for($j=0;$j -lt 3;$j++)
>> {
>> $a[$i,$j]=$i+$j
>> }
>> }
Everything work here as $b is an array of reference.

Related

How do I do operations with numeric strings inside an array (in powershell)?

So lets say my variable $a is an array containing "1" and "2" as string.
$a = "1", "2"
Now I want to use foreach through a pipeline to subtract 1 from each value, so I'd do something like
$a | foreach{$_ = [int]$_ - 1}
but this seems do nothing, yet produces no error. So $a still contains "1" and "2". I struggle to understand where I went wrong... It's possible if i don't have an array, so this works:
$b = "3"; $b - 2
And it will return 1. So I also tried without "[int]" but it still fails, so I'm guessing it either has to do with the pipeline or my foreach but I wouldn't know why it does that.
Any suggestions?
Your foreach isn't mutating the items in your original array like you think it is - you're assigning the calculated value to the context variable $_, not updating the array index.
You can either create a new array with the calculated values as follows:
$a = $a | foreach { [int]$_ - 1 }
or mutate the items in the original array in-place:
for( $i = 0; $i -lt $a.Length; $i++ )
{
$a[$i] = [int]$a[$i] - 1
}
Note that your second example doesn't quite do what you think either:
PS> $b = "3"; $b - 2
1
PS> $b
3
The $b - 2 part is an expression which is evaluated and echoed out to console - it doesn't change the value of $b because you haven't assigned the result of the expression back to anything.
Just add the instance variable to the last line of your loop like so:
$a = $a | foreach{$_ = [int]$_ - 1; $_}

How do I create an empty array of arrays in Powershell?

I want to create an empty array of arrays in Powershell to hold "tuples" of values (arrays are immutable).
Therefore I try something like:
The type of $arr is Object[]. I've read that += #(1,2) appends the given element (i.e. #(1,2)) to $arr (actually creates a new array). However, in this case it seems that the arrays are concatenated, why?
$arr = #()
$arr += #(1,2)
$arr.Length // 2 (not 1)
If I do as follows, it seems that $arr contains the two arrays #(1,2),#(3,4), which is what I want:
$arr = #()
$arr += #(1,2),#(3,4)
$arr.Length // 2
How do I initialize an empty array of arrays, such that I can add one subarray at a time, like $arr += #(1,2)?
The answer from Bruce Payette will work. The syntax seems a bit awkward to me, but it does work. At least it is not Perl.
Another way to do this would be with an ArrayList. To me, this syntax is more clear and more likely to be understood by another developer (or myself) in six months.
[System.Collections.ArrayList]$al = #()
$al.Add(#(1,2))
$al.Add(#(3,4))
foreach ($e in $al) {
$e
$e.GetType()
}
The + operator concatenates arrays. To add an array as a single element, prefix the element to add with a comma. Something like #() + , (1,2) + , (3, 4).
As far as I can tell, you can't do it natively in PowerShell, nor can you do it with the [System.Array] type. In both cases, you seem to need to define both the length and the type of the array. Because I wanted a completely empty array of arrays, with the ability to store any type of value in the array, I did it this way.
$x=[System.Collections.ArrayList]::new()
$x.Count
$x.GetType()
$x.Add([System.Collections.ArrayList]::new()) > $null
$x.Count
$x[0].Count
$x[0].GetType()
$x[0].Add("first element") > $null
$x.Count
$x[0].Count
$x[0][0]

Array.Find and IndexOf for multiple elements that are exactly the same object

I have trouble of getting index of the current element for multiple elements that are exactly the same object:
$b = "A","D","B","D","C","E","D","F"
$b | ? { $_ -contains "D" }
Alternative version:
$b = "A","D","B","D","C","E","D","F"
[Array]::FindAll($b, [Predicate[String]]{ $args[0] -contains "D" })
This will return:
D
D
D
But this code:
$b | % { $b.IndexOf("D") }
Alternative version:
[Array]::FindAll($b, [Predicate[String]]{ $args[0] -contains "D" }) | % { $b.IndexOf($_) }
Returns:
1
1
1
so it's pointing at the index of the first element. How to get indexes of the other elements?
You can do this:
$b = "A","D","B","D","C","E","D","F"
(0..($b.Count-1)) | where {$b[$_] -eq 'D'}
1
3
6
mjolinor's answer is conceptually elegant, but slow with large arrays, presumably due to having to build a parallel array of indices first (which is also memory-inefficient).
It is conceptually similar to the following LINQ-based solution (PSv3+), which is more memory-efficient and about twice as fast, but still slow:
$arr = 'A','D','B','D','C','E','D','F'
[Linq.Enumerable]::Where(
[Linq.Enumerable]::Range(0, $arr.Length),
[Func[int, bool]] { param($i) $arr[$i] -eq 'D' }
)
While any PowerShell looping solution is ultimately slow compared to a compiled language, the following alternative, while more verbose, is still much faster with large arrays:
PS C:\> & { param($arr, $val)
$i = 0
foreach ($el in $arr) { if ($el -eq $val) { $i } ++$i }
} ('A','D','B','D','C','E','D','F') 'D'
1
3
6
Note:
Perhaps surprisingly, this solution is even faster than Matt's solution, which calls [array]::IndexOf() in a loop instead of enumerating all elements.
Use of a script block (invoked with call operator & and arguments), while not strictly necessary, is used to prevent polluting the enclosing scope with helper variable $i.
The foreach statement is faster than the Foreach-Object cmdlet (whose built-in aliases are % and, confusingly, also foreach).
Simply (implicitly) outputting $i for each match makes PowerShell collect multiple results in an array.
If only one index is found, you'll get a scalar [int] instance instead; wrap the whole command in #(...) to ensure that you always get an array.
While $i by itself outputs the value of $i, ++$i by design does NOT (though you could use (++$i) to achieve that, if needed).
Unlike Array.IndexOf(), PowerShell's -eq operator is case-insensitive by default; for case-sensitivity, use -ceq instead.
It's easy to turn the above into a (simple) function (note that the parameters are purposely untyped, for flexibility):
function get-IndicesOf($Array, $Value) {
$i = 0
foreach ($el in $Array) {
if ($el -eq $Value) { $i }
++$i
}
}
# Sample call
PS C:\> get-IndicesOf ('A','D','B','D','C','E','D','F') 'D'
1
3
6
You would still need to loop with the static methods from [array] but if you are still curious something like this would work.
$b = "A","D","B","D","C","E","D","F"
$results = #()
$singleIndex = -1
Do{
$singleIndex = [array]::IndexOf($b,"D",$singleIndex + 1)
If($singleIndex -ge 0){$results += $singleIndex}
}While($singleIndex -ge 0)
$results
1
3
6
Loop until a match is not found. Assume the match at first by assigning the $singleIndex to -1 ( Which is what a non match would return). When a match is found add the index to a results array.

Powershell array is not cleared

My source code:
# $arr = #(); results in same behaviour
$arr = New-Object System.Collections.ArrayList;
$arr.Count;
$arr += "z";
$arr.Count;
$arr.Clear();
$arr.Count;
Output:
0
1
1
Powershell does some array-casting trickery when you do +=, so the easy solution is to do $arr.Add("z"). Then $arr.Clear() will act like you expect.
To clarify:
#() is a Powershell array. It uses +=, but you can't Clear it. (You can, however, do $arr = #() again to reset it to an empty array.)
ArrayList is the .NET collection. It uses .Add, and you can Clear it, but for some reason if you += it, Powershell does some weird array coercion. (If any experts care to comment on this, awesome.)

Extract a range of array in Powershell

Assume there is an array in variable $a that is created like this,
$a = ,#(1,2,3)
$a += ,#(4,5,6)
$a += ,#(7,8,9)
$a += ,#(10,11,12)
I want to extract part of the array, say $a[1] and $a[2], into another variable, say, $b such that,
$b[0] = #(4,5,6)
$b[1] = #(7,8,9)
I can use a simple for loop to do the task, but I am thinking if there is a more 'elegant' way to do this... may be a one-liner?
Thanks in advance.
You can use the Range operator to slice the array:
$b = $a[1..2]
It is worth noting the Range operator supports dynamic values - very useful when you want to work out your range dynamically, for example:
$a = #(0,1,2,3,7)
$b = #(4,5,6)
$twotoseven = $a[($a.Length-($a.Length-2))..($a.Length-2)] + $b + $a[-1]
Output:
2 3 4 5 6 7

Resources