Array.Add vs += - arrays

I've found some interesting behaviour in PowerShell Arrays, namely, if I declare an array as:
$array = #()
And then try to add items to it using the $array.Add("item") method, I receive the following error:
Exception calling "Add" with "1" argument(s): "Collection was of a fixed size."
However, if I append items using $array += "item", the item is accepted without a problem and the "fixed size" restriction doesn't seem to apply.
Why is this?

When using the $array.Add()-method, you're trying to add the element into the existing array. An array is a collection of fixed size, so you will receive an error because it can't be extended.
$array += $element creates a new array with the same elements as old one + the new item, and this new larger array replaces the old one in the $array-variable
You can use the += operator to add an element to an array. When you
use
it, Windows PowerShell actually creates a new array with the values of the
original array and the added value. For example, to add an element with a
value of 200 to the array in the $a variable, type:
$a += 200
Source: about_Arrays
+= is an expensive operation, so when you need to add many items you should try to add them in as few operations as possible, ex:
$arr = 1..3 #Array
$arr += (4..5) #Combine with another array in a single write-operation
$arr.Count
5
If that's not possible, consider using a more efficient collection like List or ArrayList (see the other answer).

If you want a dynamically sized array, then you should make a list. Not only will you get the .Add() functionality, but as #frode-f explains, dynamic arrays are more memory efficient and a better practice anyway.
And it's so easy to use.
Instead of your array declaration, try this:
$outItems = New-Object System.Collections.Generic.List[System.Object]
Adding items is simple.
$outItems.Add(1)
$outItems.Add("hi")
And if you really want an array when you're done, there's a function for that too.
$outItems.ToArray()

The most common idiom for creating an array without using the inefficient += is something like this, from the output of a loop:
$array = foreach($i in 1..10) {
$i
}
$array
Adding to a preexisting array:
[collections.arraylist]$array = 1..10
$array.add(11) > $null

Related

Find ArrayList members and (Get-Process).id vs (1,2,3).ToString()

Let us look at the code below:
$r = New-Object System.Random
$r.GetType()
$r | Get-Member
I think I understand how it works. However, when I replace Random object with ArrayList:
$a = New-Object System.Collections.ArrayList
$a.GetType()
$a | Get-Member
[Question 1] Get-Member call produces an error: "You must specify an object for the Get-Member cmdlet". What? $a is not an object? "$a.gettype()" says it is.
[Question 2] I understand ArrayList can hold other objects, but how do I get members for ArrayList itself, such as add(), clear(), etc which I found from the documentation? ISE also knows the members.
Let us look at the following 2 similar constructs:
$p = Get-Process
$p.GetType()
$p | Get-Member
$p.id[2]
$L = #(3,1,4,1,5,9,2,6,5,3)
$L.GetType()
$L | Get-Member
$L.ToString()[2]
[Question 3] $p is an array [of process objects], however, Get-Member does not show members of the array itself, but the members of the objects the array hold, does this make sense?
[Question 4] The array $p does not have a member "id", but $p.id operates on each element of the array, generating a list of #($p[0].id, $p[1].id, ...). Let us accept this is how it works in Powweshell, then the same should apply to to array $L, however, $L.ToString() results the literal string "System.Object[]", those exact 15 chars! And $L.ToString()[2] is "s"! Why there is no consistency?
[Edit] Question 4 used a bad example because ToString() is a method for the array as well as the number element. ToString() method on an array is to return the object type "System.Object[]". A better example is:
#([byte]67, [byte]97, [byte]116).tochar($null)
C
a
t
which shows that operation on a list, when not a list operation, operates on the element (unrolls) and then re-rolls back to a list. It seems to be Powershell is pragmatic, but at least consistent.
As Mr Lee_Dailey said, $listofsomethings | get-member will apply to the actual objects in the variable. If they are all the same, powershell can figure this out and will only output the information once. However, look at this example.
$array = [int]2,[string]"doug",[float]19.83
$array | Get-Member
You'll see it gives you the details for 3 distinct types, as there are three different types in this array. On the other hand, if you do
Get-Member -InputObject $array
You will see information about the array container itself. Additionally, the following command also looks at the container as well.
$array.GetType().BaseType
To see the type of each member in the container's collection (not using the powershell auto unrolling of $array | gm) you'd have to loop over them.
$array.ForEach{$_.gettype()}
$array.ForEach{$_.gettype().basetype}
Now knowing this, it should be clear why $p.id works.. Powershell is "helping" you by effectively running this under the hood
$p | foreach id
Or written the same way as the $array foreach
$p.foreach{$_.id}
$arraylists are like arrays in that they can contain different type of objects.
$arraylist = New-Object System.Collections.ArrayList
$arraylist.Add([int]2)
$arraylist.Add([string]"Stack Overflow")
$arraylist.Add([double]32.80)
You'll notice that each time you add an item to the arraylist, powershell outputs the index of where that item was added. It's commonplace to remove this $null = $arraylist.add(...). This annoyance alone should be enough to discourage it's use, but the fact that it's deprecated in favor of the System.Collections.Generic.List lists should already lead you away from it. You specify the type of item the list is for such as [string],[int],[object],etc. It also doesn't have the side effect of spitting out index numbers when adding items.
You can create a list like this
$list = new-object System.Collections.Generic.List[string]
or
$list = [System.Collections.Generic.List[string]]::new()
And you can add/remove the same way you do with an arraylist.

Data Rows to String to Array issue

I have a very weird problem happening. I have an object populated with a bunch of rows. I can access them all well, but I need to append a "." (dot) to every value inside it, so I end up converting each record to a string using a for each loop and adding a "." after trimming the values. The issue now however, is that I would like to assign each of these rows (And the rows have only one column/Item) to another array/object, so I can access them later.
The issue is that even when I declare an object/array variable and assign the string converted data rows, the object variable keeps getting converted to a String, and I just cant seem to avoid it.
Please help. Here is a modified code sample:
[String] $DBNms = #();
ForEach($DBName in $objDBNms){
$tempText += $DBName.Item(0).ToString().Trim() + "."
$DBNms += $tempText
}
Write-Host($DBNms.GetType()) #And this is showing up a string, while I want it to be an array.
And if I print $DBNms, it indeed shows up a string concatenated together into a single string, while I actually want it to be like $DBNms[0] = first Item value, $DBNms[1] = second Item value and so on.
[String] $DBNms = #(); makes $DBNms a type-constrained variable, due to type literal [string] being placed to the left of variable $DBNms, whose type is then locked in as a string; that is, as a single string.
You were looking to create a string array, which in PowerShell is represented as [string[]]:
[string[]] $DBNames = #()
PowerShell's type conversions are automatic (and very flexible), so that [String] $DBNms = #() doesn't report an error, it quietly converts the empty array (#()) to the empty string (as required by the type constraint):
PS> [string] $DBNames = #(); '' -eq $DBNames
True
A much more efficient way to collect values from multiple iterations in an array is to use the foreach statement as an expression, which case PowerShell collects the outputs automatically for you:
[string[]] $DBNms = foreach ($DBName in $objDBNms){
# Output this expression's value as-is.
# PowerShell will collect the individual iterations' values for you.
$DBName.Item(0).ToString().Trim() + "."
}
The above is not only more concise than your original approach, it is more importantly more efficient:
In reality you cannot directly add (append to) an array, because it is an immutable data structure.
What PowerShell has to do whenever you use += with an array is to allocate a new array behind the scenes, with the original elements copied over and the new element(s) appended; the new array is then automatically assigned back to the variable.
Note: The alternative and next best solution to using the whole loop as an expression is to use an efficiently extensible list type, notably [System.Collections.Generic.List[object]](System.Collections.Generic.List`1):
# Create the list.
# The 'System.' namespace prefix is optional.
[Collections.Generic.List[string]] $DBNms = #()
foreach ($DBName in $objDBNms) {
# Add to the list, using its .Add() method.
# Note: Do NOT try to add with +=
$DBNms.Add($DBName.Item(0).ToString().Trim() + ".")
}
You'll need this approach if your loop does more than outputting a single value per iteration, such as needing to append to multiple collections.
Note: It used to be common to use System.Collections.ArrayList instances instead; however:
Use of this type is no longer recommended (see the warning in the linked help topic); use [System.Collections.Generic.List[object]] instead, which additionally allows you to strongly type the collection (replace [object] with the type of interest, such as [string] in the code above).
It has the drawback of its .Add() method having a return value, which you need to silence explicitly (e.g., $null = $lst.Add(...)), so that it doesn't accidentally pollute your code's output stream (produce unexpected extra output).

How to dim an empty array of a specific length

I need to copy a collection of 10 items into an array so I can index into it. So I need to use the CopyTo method and specify a destination array large enough to accommodate the collection.
So before calling CopyTo I need to dim an empty array of a specific size. I can't seem to find the correct PowerShell syntax for doing this.
This didn't work for me:
$snapshot = New-Object System.Array 10;
New-Object : Constructor not found. Cannot find an appropriate constructor for type System.Array.
Edit
Have worked around it with:
$snapshot = New-Object System.Collections.ArrayList;
$snapshot.AddRange($slider);
I think this should do the trick:
$snapshot = #()
Edit:
Ok, sorry about that.
Tested this one:
> $snapshot = New-Object object[] 10
> $snapshot.Length
10
I think just creating a new array will accomplish that in Powershell.
# A "collection of ten items"
$collection = #(1,2,3,4,5,6,7,8,9,0)
#This command creates a new array, sizes it, and copies data into the new array
$newCollection = $collection
Powershell creates a new array in this case. No need to specify the size of the array; Powershell sizes it automatically. And if you add an element to the array, Powershell will take care of the tedious initializeNew-copy-deleteOld routine you have to do in lower-level languages.
Note that $newCollection isn't a reference to the same array that $collection references. It's a reference to a whole new array. Modifying $collection won't affect $newCollection, and vice-versa.

Powershell: get data out of array and put it into into new array

I'm trying to get data out of an array by using following command:
$newarray = $current_ds2_data -match $codenumber
In this case the $current_ds2_data is an array as a result of the "Import-Csv" command and the $codenumber contains the value I want to search for in the array. This work OK.
Following is an example of the value of $newarray:
P_SYS_InternalName : #D_OCEV_ABC-
P_OCEV_Price : 0.15
P_NDS_ValidPN : 12345678
P_OCEV_PriceUnit :
P_NDS_VersionNumber : 1
Now I want to modify the value of the P_OCEV_Price field by doing
$newarray.P_OCEV_Price = 0.2
however this doesn't seem to work. It appears that $newarray.P_OCEV_Price contains no value. Somehow PS doesn't recognize P_OCEV_Price to be a cell of the array.
I also tried using
$newarray.["P_OCEV_Price"] = 0.2
to comply with hash-table formating
Next to this I tried defining the $newarray explicitly as an array or hash-table by using
$newarray = #()
or
$newarray = #{}
So far nothing seems to work. What am I doing wrong??
Since your $newarray variable is an array, you won't be able to use the simple $newarray.P_OCEV_Price syntax to change that value. Here are two alternate options that may help you, depending on your source data:
# Change the price of the first matching item
$newarray[0].P_OCEV_Price = 0.2
# Change the price for all matching items
$newarray | Foreach-Object { $_.P_OCEV_Price = 0.2 }
In cases like this, I usually like to point out that arrays of size 1 are easy to confuse with single objects in Powershell. If you try looking at $newarray and $newarray[0] with simple output statements the results will probably look identical. It's a good idea to keep GetType() handy in these cases.
# This will show a type of Object[]
$newarray.GetType()
# The type here will show PSCustomObject instead
($newarray[0]).GetType()

Perl: Hash within Array within Hash

I am trying to build a Hash that has an array as one value; this array will then contain hashes. Unfortunately, I have coded it wrong and it is being interpreted as a psuedo-hash. Please help!
my $xcHash = {};
my $xcLine;
#populate hash header
$xcHash->{XC_HASH_LINES} = ();
#for each line of data
$xcLine = {};
#populate line hash
push(#{$xcHash->{XC_HASH_LINES}}, $xcLine);
foreach $xcLine ($xcHash->{XC_HASH_LINES})
#psuedo-hash error occurs when I try to use $xcLine->{...}
$xcHash->{XC_HASH_LINES} is an arrayref and not an array. So
$xcHash->{XC_HASH_LINES} = ();
should be:
$xcHash->{XC_HASH_LINES} = [];
foreach takes a list. It can be a list containing a single scalar (foreach ($foo)), but that's not what you want here.
foreach $xcLine ($xcHash->{XC_HASH_LINES})
should be:
foreach my $xcLine (#{$xcHash->{XC_HASH_LINES}})
foreach $xcLine ($xcHash->{XC_HASH_LINES})
should be
foreach $xcLine ( #{ $xcHash->{XC_HASH_LINES} } )
See http://perlmonks.org/?node=References+quick+reference for easy to remember rules for how to dereference complex data structures.
Golden Rule #1
use strict;
use warnings;
It might seem like a fight at the beginning, but they will instill good Perl practices and help identify many syntactical errors that might otherwise go unnoticed.
Also, Perl has a neat feature called autovivification. It means that $xcHash and $xcLine need not be pre-defined or constructed as references to arrays or hashes.
The issue faced here is to do with the not uncommon notion that a scalar can hold an array or hash; it doesn't. What it holds is a reference. This means that the $xcHash->{XC_HASH_LINES} is an arrayref, not an array, which is why it needs to be dereferenced as an array using the #{...} notation.
Here's what I would do:
my %xcHash;
for each line of data:
push #{$xcHash{XC_HASH_LINES}},$xcLine;

Resources