88

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
2
  • 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. Commented Oct 22, 2008 at 17:07
  • I did not see anyone mention specifically that arrays are immutable. Once created, they cannot be modified. Commented Feb 15, 2019 at 12:03

13 Answers 13

109

Here's two more ways, both very concise.

$arr1 = @(0) * 20
$arr2 = ,0 * 20
6
  • Very nice, I was trying to figure this out this morning and I think you gave the most concise way to initialize an array. Commented Nov 20, 2008 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. Commented Jul 24, 2014 at 13:54
  • looks like powershell arrays take the multiplication operator, which simply makes copies of itself that many times. pretty cool.
    – Nacht
    Commented Mar 6, 2015 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
    Commented Apr 22, 2015 at 15:44
  • 20 denotes the number of array elements to be initialized. Hence, $arr1[21] = ... will yield an error.
    – jamacoe
    Commented Sep 2, 2021 at 7:47
58

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!

2
  • 1
    Or: $a = @(); $a += ...
    – marsze
    Commented Feb 11, 2016 at 8:15
  • 3
    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) Commented Feb 13, 2019 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.

4
  • 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
    Commented Jan 14, 2015 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
    Commented Mar 6, 2015 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. Commented Mar 9, 2015 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
    Commented Feb 11, 2016 at 8: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.

1
  • 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. Commented Mar 24, 2016 at 15:26
14
$array = 1..5 | foreach { $false }
1
  • 1
    I like this, I dropped a % in place of the foreach and it gives it a really tight initialization. Commented Nov 20, 2008 at 16:24
14

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

1
  • 1
    ::new() is handy indeed; worth mentioning that it require PSv5+.
    – mklement0
    Commented Jul 10, 2018 at 21:20
11
$array = @()
for($i=0; $i -lt 5; $i++)
{
    $array += $i
}
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
}
0
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)
}
4
  • Why use this instead of an array and += ? Commented Dec 1, 2011 at 6:41
  • 1
    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
    Commented Dec 1, 2011 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
    Commented Dec 1, 2011 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
    Commented Jun 5, 2017 at 19:05
1

Here's another typical way:

$array = for($i = 0; $i -le 4; $i++) { $false }
0

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

[bool[]]$tf=((,$False)*5)
1
  • Measuring this command with 100000 elements instead of 5 resulted in 526247 ticks. Measuring @(false) * 100000 resulted in 91802 ticks. Commented Feb 15, 2019 at 12:01
0
$array = foreach($i in 1..5) { $false }
0

$server = "name"

[string[]]$array_white_list = Get-Content -Path "$home\MonTool\whitelist.txt"

Set-Content -Path "$home\MonTool\signaleringen.txt" -Value "Server: $server" Add-Content -Path "$home\MonTool\signaleringen.txt" -Value "Datum/Tijd: $datum_tijd"

Add-Content -Path "$home\MonTool\signaleringen.txt" -Value $proces_naam

Write-Warning 'De opgegeven servernaam is niet bekend; het script wordt gestopt.'

Write-Host "Datum/Tijd: $datum_tijd" Write-Host "Het script is klaar met de uitvoering. Tot de volgende keer."

1
  • Thank you for your interest in contributing to the Stack Overflow community. This question already has quite a few answers—including one that has been extensively validated by the community. Are you certain your approach hasn’t been given previously? If so, it would be useful to explain how your approach is different, under what circumstances your approach might be preferred, and/or why you think the previous answers aren’t sufficient. Can you kindly edit your answer to offer an explanation? Commented May 26, 2024 at 0:58

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.