85

What's the best way to initialize an array in PowerShell?

For example, the code

$array = @()
for($i=0; $i -lt 5;$i++)
{
    $array[$i] = $FALSE
}

generates the error

Array assignment failed because index '0' was out of range.
At H:\Software\PowerShell\TestArray.ps1:4 char:10
+         $array[$ <<<< i] = $FALSE
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Eric Ness
  • 10,119
  • 15
  • 48
  • 51
  • 1
    tell us what you're trying to accomplish and maybe we'll be able to provide you a better "idiomatic PowerShell" answer. I've never needed to new up an array in PowerShell. – Peter Seale Oct 22 '08 at 17:07
  • I did not see anyone mention specifically that arrays are immutable. Once created, they cannot be modified. – AdminOfThings Feb 15 '19 at 12:03

12 Answers12

106

Here's two more ways, both very concise.

$arr1 = @(0) * 20
$arr2 = ,0 * 20
halr9000
  • 9,879
  • 5
  • 33
  • 34
  • Very nice, I was trying to figure this out this morning and I think you gave the most concise way to initialize an array. – Chris Sutton Nov 20 '08 at 16:19
  • I must be thick. Can someone explain what this is doing and what the * 20 is for? 20 doesn't appear in anyone else's answer, or the question. – Luke Puplett Jul 24 '14 at 13:54
  • looks like powershell arrays take the multiplication operator, which simply makes copies of itself that many times. pretty cool. – Nacht Mar 06 '15 at 23:34
  • 1
    @halr9000 your link is broken. This is why we find it useful to put the relevant information inside the answer itself. :) – Cullub Apr 22 '15 at 15:44
  • 20 denotes the number of array elements to be initialized. Hence, $arr1[21] = ... will yield an error. – jamacoe Sep 02 '21 at 07:47
56

You can also rely on the default value of the constructor if you wish to create a typed array:

> $a = new-object bool[] 5
> $a
False
False
False
False
False

The default value of a bool is apparently false so this works in your case. Likewise if you create a typed int[] array, you'll get the default value of 0.

Another cool way that I use to initialze arrays is with the following shorthand:

> $a = ($false, $false, $false, $false, $false)
> $a
False
False
False
False
False

Or if you can you want to initialize a range, I've sometimes found this useful:

> $a = (1..5)   
> $a
1
2
3
4
5

Hope this was somewhat helpful!

Scott Saad
  • 17,962
  • 11
  • 63
  • 84
  • 1
    Or: `$a = @(); $a += ...` – marsze Feb 11 '16 at 08:15
  • 2
    A newer (PS 5.0) alternative to New-Object that I find more readable (Intellisense available in ISE) : 1-dimension array: `$array = [int[]]::new(5)`. 2-dimensions array: `$array = [int[][]]::new(5,3)` – Petru Zaharia Feb 13 '19 at 19:58
49

Yet another alternative:

for ($i = 0; $i -lt 5; $i++) 
{ 
  $arr += @($false) 
}

This one works if $arr isn't defined yet.

NOTE - there are better (and more performant) ways to do this... see https://stackoverflow.com/a/234060/4570 below as an example.

David Mohundro
  • 11,922
  • 5
  • 40
  • 44
  • 13
    This is super slow. Since .NET arrays cannot be resized, this essentially allocates new array with additional space for new item and copies data, so if in the code snippet above you change `$i -lt 5` to `$i -lt 500000` you'd have wait looong time till it finishes. – n0rd Jan 14 '15 at 20:50
  • I do this method all the time, when shelling. baaad habit for when writing scripts that have to stick around. it's a completely unnecessary O(n^2) time. even when shelling i have to stop it and do it the right way sometimes. it is super convenient though. – Nacht Mar 06 '15 at 23:44
  • I typically don't use this method at all to be honest, I usually use an `ArrayList` or something similar instead. It isn't technically an array then, though. – David Mohundro Mar 09 '15 at 17:02
  • 6
    This is not the proper way to do it. It just happens to work because of the way PowerShell handles arrays. Use this: `$arr = New-Object bool[] 5` – marsze Feb 11 '16 at 08:13
43

The original example returns an error because the array is created empty, then you try to access the nth element to assign it a value.

The are a number of creative answers here, many I didn't know before reading this post. All are fine for a small array, but as n0rd points out, there are significant differences in performance.

Here I use Measure-Command to find out how long each initialization takes. As you might guess, any approach that uses an explicit PowerShell loop is slower than those that use .Net constructors or PowerShell operators (which would be compiled in IL or native code).

Summary

  • New-Object and @(somevalue)*n are fast (around 20k ticks for 100k elements).
  • Creating an array with the range operator n..m is 10x slower (200k ticks).
  • Using an ArrayList with the Add() method is 1000x slower than the baseline (20M ticks), as is looping through an already-sized array using for() or ForEach-Object (a.k.a. foreach,%).
  • Appending with += is the worst (2M ticks for just 1000 elements).

Overall, I'd say array*n is "best" because:

  • It's fast.
  • You can use any value, not just the default for the type.
  • You can create repeating values (to illustrate, type this at the powershell prompt: (1..10)*10 -join " " or ('one',2,3)*3)
  • Terse syntax.

The only drawback:

  • Non-obvious. If you haven't seen this construct before, it's not apparent what it does.

But keep in mind that for many cases where you would want to initialize the array elements to some value, then a strongly-typed array is exactly what you need. If you're initializing everything to $false, then is the array ever going to hold anything other than $false or $true? If not, then New-Object type[] n is the "best" approach.

Testing

Create and size a default array, then assign values:

PS> Measure-Command -Expression {$a = new-object object[] 100000} | Format-List -Property "Ticks"
Ticks : 20039

PS> Measure-Command -Expression {for($i=0; $i -lt $a.Length;$i++) {$a[$i] = $false}} | Format-List -Property "Ticks"
Ticks : 28866028

Creating an array of Boolean is bit little slower than and array of Object:

PS> Measure-Command -Expression {$a = New-Object bool[] 100000} | Format-List -Property "Ticks"
Ticks : 130968

It's not obvious what this does, the documentation for New-Object just says that the second parameter is an argument list which is passed to the .Net object constructor. In the case of arrays, the parameter evidently is the desired size.

Appending with +=

PS> $a=@()
PS> Measure-Command -Expression { for ($i=0; $i -lt 100000; $i++) {$a+=$false} } | Format-List -Property "Ticks"

I got tired of waiting for that to complete, so ctrl+c then:

PS> $a=@()
PS> Measure-Command -Expression { for ($i=0; $i -lt    100; $i++) {$a+=$false} } | Format-List -Property "Ticks"
Ticks : 147663
PS> $a=@()
PS> Measure-Command -Expression { for ($i=0; $i -lt   1000; $i++) {$a+=$false} } | Format-List -Property "Ticks"
Ticks : 2194398

Just as (6 * 3) is conceptually similar to (6 + 6 + 6), so ($somearray * 3) ought to give the same result as ($somearray + $somearray + $somearray). But with arrays, + is concatenation rather than addition.

If $array+=$element is slow, you might expect $array*$n to also be slow, but it's not:

PS> Measure-Command -Expression { $a = @($false) * 100000 } | Format-List -Property "Ticks"
Ticks : 20131

Just like Java has a StringBuilder class to avoid creating multiple objects when appending, so it seems PowerShell has an ArrayList.

PS> $al = New-Object System.Collections.ArrayList
PS> Measure-Command -Expression { for($i=0; $i -lt 1000; $i++) {$al.Add($false)} } | Format-List -Property "Ticks"
Ticks : 447133
PS> $al = New-Object System.Collections.ArrayList
PS> Measure-Command -Expression { for($i=0; $i -lt 10000; $i++) {$al.Add($false)} } | Format-List -Property "Ticks"
Ticks : 2097498
PS> $al = New-Object System.Collections.ArrayList
PS> Measure-Command -Expression { for($i=0; $i -lt 100000; $i++) {$al.Add($false)} } | Format-List -Property "Ticks"
Ticks : 19866894

Range operator, and Where-Object loop:

PS> Measure-Command -Expression { $a = 1..100000 } | Format-List -Property "Ticks"
Ticks : 239863
Measure-Command -Expression { $a | % {$false} } | Format-List -Property "Ticks"
Ticks : 102298091

Notes:

  • I nulled the variable between each run ($a=$null).
  • Testing was on a tablet with Atom processor; you would probably see faster speeds on other machines. [edit: About twice as fast on a desktop machine.]
  • There was a fair bit of variation when I tried multiple runs. Look for the orders of magnitude rather than exact numbers.
  • Testing was with PowerShell 3.0 in Windows 8.

Acknowledgements

Thanks to @halr9000 for array*n, @Scott Saad and Lee Desmond for New-Object, and @EBGreen for ArrayList.

Thanks to @n0rd for getting me to think about performance.

Celery Man
  • 531
  • 4
  • 4
  • Awesome breakdown. I had a nice script that used $_.split(' ')[0..1] to inspect the date and time in the IIS log, but changed to an $_.indexof approach when processing time grew exponentially looking through 100,000 log entries. – Kirt Carson Mar 24 '16 at 15:26
14
$array = 1..5 | foreach { $false }
Peter Seale
  • 4,835
  • 4
  • 37
  • 45
13

Here's another idea. You have to remember, that it's .NET underneath:

$arr = [System.Array]::CreateInstance([System.Object], 5)
$arr.GetType()
$arr.Length

$arr = [Object[]]::new(5)
$arr.GetType()
$arr.Length

Result:

IsPublic IsSerial Name                                     BaseType                                                                                               
-------- -------- ----                                     --------                                                                                               
True     True     Object[]                                 System.Array                                                                                           
5
True     True     Object[]                                 System.Array                                                                                           
5

Using new() has one distinct advantage: when you're programming in ISE and want to create an object, ISE will give you hint with all paramer combinations and their types. You don't have that with New-Object, where you have to remember the types and order of arguments.

ISE IntelliSense for new object

AdamL
  • 12,421
  • 5
  • 50
  • 74
11
$array = @()
for($i=0; $i -lt 5; $i++)
{
    $array += $i
}
Community
  • 1
  • 1
EBGreen
  • 36,735
  • 12
  • 65
  • 85
7

The solution I found was to use the New-Object cmdlet to initialize an array of the proper size.

$array = new-object object[] 5 
for($i=0; $i -lt $array.Length;$i++)
{
    $array[$i] = $FALSE
}
Eric Ness
  • 10,119
  • 15
  • 48
  • 51
7

If I don't know the size up front, I use an arraylist instead of an array.

$al = New-Object System.Collections.ArrayList
for($i=0; $i -lt 5; $i++)
{
    $al.Add($i)
}
mhu
  • 17,720
  • 10
  • 62
  • 93
EBGreen
  • 36,735
  • 12
  • 65
  • 85
  • Why use this instead of an array and += ? – Ryan Fisher Dec 01 '11 at 06:41
  • No particular reason. Just habits from more restrictive languages that require pre-dimensioning of array sizes. Plus I like the extra features built into arraylists. – EBGreen Dec 01 '11 at 16:56
  • Also if you notice, I did also provide an array += answer. This was all done over 3 years ago before the way that SO should work was really defined. Today I would put both methods into one answer. – EBGreen Dec 01 '11 at 16:57
  • 4
    @RyanFisher Arrays can't be resized, so using += will make a full copy of the entire array every time you call +=. That means += is O(n), while ArrayList.Add() is O(1). In my experience, if you're doing anything remotely fiddley with arrays, you'll be better off using an ArrayList. – Bacon Bits Jun 05 '17 at 19:05
1

Here's another typical way:

$array = for($i = 0; $i -le 4; $i++) { $false }
js2010
  • 23,033
  • 6
  • 64
  • 66
0

Or try this an idea. Works with powershell 5.0+.

[bool[]]$tf=((,$False)*5)
cosmoonot
  • 2,161
  • 3
  • 32
  • 38
  • Measuring this command with 100000 elements instead of 5 resulted in 526247 ticks. Measuring `@(false) * 100000` resulted in 91802 ticks. – AdminOfThings Feb 15 '19 at 12:01
0
$array = foreach($i in 1..5) { $false }
MarredCheese
  • 17,541
  • 8
  • 92
  • 91