PowerShell: Difference between array notation? - arrays

Is there a difference between these two array creation statements? So, is '#' sign optional when creating arrays?
$a = "This", "Is", "a", "cat"
$a.GetType()
$a | gm
$a = #("This", "Is", "a", "cat")
$a.GetType()
$a | gm

$a = #() # declare an empty array.
$a = #(mysingleitem) # declare an array with a single element
In other case is optional.

Is there a difference between these two array creation statements?
Though I am not 100% sure (it depends on PowerShell guts) the difference may be the following: "This", "Is", "a", "cat" creates an array. #("This", "Is", "a", "cat") creates the same array and then applies the operator #() to it (apparently redundant operation in this particular case).
Using, for example, this profiler we can see that the second expression is quite slower (14% or something), so that my guess may be correct. Ideally, PowerShell code interpretator could treat these two expressions in the same way but it probably does not.
See also the help topic (the end, about operators #() and ,)
help about_operators

Related

Remove array from array in powershell

I have 2 arrays like this:
$array1 = #('Item10', 'Item20', 'Item30', 'Item40')
$array2 = #('Item1', 'Item3')
I would like to have my final array like this:
$arrayfinal = #('Item30', 'Item40')
meaning: find 'like' Item1 from array2 in array1, so remove it !
I tried: with the '-like' operator, the '-ne' operator, tried to add '*' and use the 'like' operator but could not find what I want.
Thanks for your help
You need to test each value in $array1 against each possible prefix from $array2 until you get a match (or not) - in other words, nested filters.
My preferred approach is to nest .Where({...}, 'First') inside Where-Object:
$arrayfinal = $array1 |Where-Object {$item = $_; -not $array2.Where({$item -like "${_}*"}, 'First')}
The 'First' mode will make .Where() return as soon as the first item satisfies the condition, thereby not needlessly going through all of $array2 on each iteration.

PowerShell: Why does [Parameter(Mandatory = $True)] on one parameter affect type casting behavior of another parameter?

Does someone know why Mandatory attribute on parameter $a affects the type casting behaviour of parameter $b? In the example the array should be cast to a string.
function test ([Parameter(Mandatory = $True)] [string] $a, [string] $b) {
$a; $b
}
$b = "a", "b", "c"
test -a "my string" -b $b
When this code chunk is executed it produces error:
test : Cannot process argument transformation on parameter 'b'. Cannot convert value to type System.String.
At line:1 char:31
If I remove the Mandatory attribute from $a it works fine:
function test ([string] $a, [string] $b) {
$a; $b
}
$b = "a", "b", "c"
test -a "my string" -b $b
Thanks in advance for feedback
By adding the [Parameter()] attribute, you are implying the [CmdletBinding()] attribute (i.e. turning the function into an Advanced Function). If you look at the source code for the behaviour when using [CmdletBinding()], you'll see that it explicitly disallows conversion of an array to a string, which is what you're trying to do.
To see that it is specific to arrays, try, for example, $b = Get-Date (i.e. pass a DateTime object). The conversion to string works fine.
To complement boxdog's helpful answer:
Indeed, the difference in behavior comes down to whether your function is a simple one (no [CmdletBinding()] attribute and/or [Parameter()] attributes) or an advanced - cmdlet-like - one.
Despite PowerShell's generally very helpful automatic type conversions, you could argue that even simple functions should never have allowed implicitly converting an array to a scalar [string] parameter value, because:
The way that arrays are stringified (converted to a single string) is somewhat arbitrary: by default, the (stringified) elements of the array are space-separated (a script block ({...}) without the aforementioned parameters is effectively a simple function).
& { param([string] $s) "[$s]" } 'one', 'two' # -> '[one two]'
What's more, this behavior depends on whether the $OFS preference variable was set to an arbitrary separator string.
$OFS='!'; & { param([string] $s) "[$s]" } 'one', 'two' # -> '[one!two]'
In short:
It would be reasonable to always require deliberate conversion of an array meant to be passed to a parameter that expects a scalar.
However, given PowerShell's commitment to backward compatibility, the existing behavior of simple functions is unlikely to change.

powershell array clear method and assignment

The assignment operator = “Sets the value of a variable to the specified value” as said in the reference doc . Not surprisingly, changes on a variable (in my case an array) that has been previously assigned to another variable do not affect the value of this latter one.
PS C:\> $V="a", "b", "c"
PS C:\> $A=$V
PS C:\> write-host "A before new value of V: $A"
A before new value of V: a b c
PS C:\> $V="e","f"
PS C:\> write-host "A after new value of V: $A"
A after new value of V: a b c
PS C:\>
But when the method clear() is used the behaviour seems different.
PS C:\> $V="a", "b", "c"
PS C:\> $A=$V
PS C:\> write-host "A before clearing V: $A"
A before clearing V: a b c
PS C:\> $V.clear()
PS C:\> write-host "A after clearing V: $A"
A after clearing V:
PS C:\>
It seems that clear() method applied on $V acts also on $A. As if the assignment were by reference, strange enough, only for this method. In fact, if a new value is assigned to $V after having cleared it, $A is still affected only by the clear.
PS C:\> $V="a", "b", "c"
PS C:\> $A=$V
PS C:\> write-host "A before clearing V: $A"
A before clearing V: a b c
PS C:\> $V.clear()
PS C:\> $V="e","f"
PS C:\> write-host "A after clearing V: $A"
A after clearing V:
PS C:\>
There are possibilities to avoid this effect, although not precisely identical: $A=$V.clone() or use of cmdlet Clear-Variable -name V or $V=$null instead $V.clear() or perhaps others better than these that somebody could suggest.
But my question is:
how to explain the "propagation" of the effect of clear method on $V to the other array $A?
Tests have been done on PS ver.5.1.
Use Clone() method to get true copy of an array.
$V = "a", "b", "c"
$A = $V.Clone()
Write-Host "A before new value of V: $A"
$V = "e","f"
Write-Host "A after new value of V: $A"
A before new value of V: a b c
A after new value of V: a b c
For explanation, read Copying Arrays and Hash Tables:
Copying arrays or hash tables from one variable to another works, but
may produce unexpected results. The reason is that arrays and hash
tables are not stored directly in variables, which always store only a
single value. When you work with arrays and hash tables, you are
dealing with a reference to the array or hash table. So, if you
copy the contents of a variable to another, only the reference will be
copied, not the array or the hash table. That could result in the
following unexpected behavior:
$array1 = 1,2,3
$array2 = $array1
$array2[0] = 99
$array1[0]
99
Although the contents of $array2 were changed in this example, this
affects $array1 as well, because they are both identical. The
variables $array1 and $array2 internally reference the same
storage area. Therefore, you have to create a copy if you want to
copy arrays or hash tables,:
$array1 = 1,2,3
$array2 = $array1.Clone()
$array2[0] = 99
$array1[0]
1
Whenever you add new elements to an array (or a hash table) or remove
existing ones, a copy action takes place automatically in the
background and its results are stored in a new array or hash table.
The following example clearly shows the consequences:
# Create array and store pointer to array in $array2:
$array1 = 1,2,3
$array2 = $array1
# Assign a new element to $array2. A new array is created in the process and stored in $array2:
$array2 += 4
$array2[0]=99
# $array1 continues to point to the old array:
$array1[0]
1
BTW, you can meet the terms Value type, Reference type and Pointer type more often…
For me what you see is normal, I try to explain myself :
$V="a", "b", "c"
$a = $V
now $a contains the same reference as $V. if you clear $V then $a is cleared.
now if you write $V = "b","c", you affect $V with the reference of a new tab. and this reference is not the same that you affect to $a. so now if you clear $V, $a is not cleared.
Am I clear enought ?

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]

Appending a string to each item of an array

I have an array and when I try to append a string to it the array converts to a single string.
I have the following data in an array:
$Str
451 CAR,-3 ,7 ,10 ,0 ,3 , 20 ,Over: 41
452 DEN «,40.5,0,7,0,14, 21 ,  Cover: 4
And I want to append the week of the game in this instance like this:
$Str = "Week"+$Week+$Str
I get a single string:
Week16101,NYG,42.5 ,3 ,10 ,3 ,3 , 19 ,Over 43 102,PHI,- 1,14,7,0,3, 24 ,  Cover 4 103,
Of course I'd like the append to occur on each row.
Instead of a for loop you could also use the Foreach-Object cmdlet (if you prefer using the pipeline):
$str = "apple","lemon","toast"
$str = $str | ForEach-Object {"Week$_"}
Output:
Weekapple
Weeklemon
Weektoast
Another option for PowerShell v4+
$str = $str.ForEach({ "Week" + $Week + $_ })
Something like this will work for prepending/appending text to each line in an array.
Set array $str:
$str = "apple","lemon","toast"
$str
apple
lemon
toast
Prepend text now:
for ($i=0; $i -lt $Str.Count; $i++) {
$str[$i] = "yogurt" + $str[$i]
}
$str
yogurtapple
yogurtlemon
yogurttoast
This works for prepending/appending static text to each line. If you need to insert a changing variable this may require some modification. I would need to see more code in order to recommend something.
Another solution, which is fast and concise, albeit a bit obscure.
It uses the regex-based -replace operator with regex '^' which matches the position at the start of each input string and therefore effectively prepends the replacement string to each array element (analogously, you could use '$' to append):
# Sample array.
$array = 'one', 'two', 'three'
# Prepend 'Week ' to each element and create a new array.
$newArray = $array -replace '^', 'Week '
$newArray then contains 'Week one', 'Week two', 'Week three'
To show an equivalent foreach solution, which is syntactically simpler than a for solution (but, like the -replace solution above, invariably creates a new array):
[array] $newArray = foreach ($element in $array) { 'Week ' + $element }
Note: The [array] cast is needed to ensure that the result is always an array; without it, if the input array happens to contain just one element, PowerShell would assign the modified copy of that element as-is to $newArray; that is, no array would be created.
As for what you tried:
"Week"+$Week+$Str
Because the LHS of the + operation is a single string, simple string concatenation takes place, which means that the array in $str is stringified, which by default concatenates the (stringified) elements with a space character.
A simplified example:
PS> 'foo: ' + ('bar', 'baz')
foo: bar baz
Solution options:
For per-element operations on an array, you need one of the following:
A loop statement, such as foreach or for.
Michael Timmerman's answer shows a for solution, which - while syntactically more cumbersome than a foreach solution - has the advantage of updating the array in place.
A pipeline that performs per-element processing via the ForEach-Object cmdlet, as shown in Martin Brandl's answer.
An expression that uses the .ForEach() array method, as shown in Patrick Meinecke's answer.
An expression that uses an operator that accepts arrays as its LHS operand and then operates on each element, such as the -replace solution shown above.
Tradeoffs:
Speed:
An operator-based solution is fastest, followed by for / foreach, .ForEach(), and, the slowest option, ForEach-Object.
Memory use:
Only the for option with indexed access to the array elements allows in-place updating of the input array; all other methods create a new array.[1]
[1] Strictly speaking, what .ForEach() returns isn't a .NET array, but a collection of type [System.Collections.ObjectModel.Collection[psobject]], but the difference usually doesn't matter in PowerShell.

Resources