08 December 2007

Slicing PowerShell Arrays and Ranges

Introduction

You can slice (or extract elements) from an array in PowerShell in several ways. Let's start with a simple array of numbers. We start with 0, because PowerShell's array indices start from 0 and to make it easier to see the effect of each statement. We use the Write-Host cmdlet where necessary to keep the number of lines in the output to a minimum.

> $a = 0,1,2,3,4,5
> $a.length
6
> Write-Host $a
0 1 2 3 4 5

The rest of this article describes how to obtain a single item, multiple items and a reverse slice, and how to combine an index list with a range.

Get an Item

Let's retrieve an item from different indices in our array, $a:

> $a[0]
0
> $a[$a.length]
> $a[-$a.length-1]
> $a[$a.length-1] -eq $a[-1]
True
> $a[$a.length-2] -eq $a[-2]
True

The first item is in index 0 and the last index is $a.length-1. Also, $a.length-x is the same as -x. If you try to access an index value greater than $a.length-1 (e.g. 6) or less than -$a.length (e.g. -7), you will get nothing.

Get Arbitrary Set of Items

We can also retrieve multiple items and items more than once.

> Write-Host $a[0,-1,5,2,4,1]
0 5 5 2 4 1

Get Multiple Items Using a Range

We can also retrieve a range of values using the .. (dot-dot) operator.

> Write-Host $a[0..2]
0 1 2
> Write-Host $a[2..5]
2 3 4 5
> Write-Host $a[0..($a.length-1)]
0 1 2 3 4 5

We enclose the arithmetic expression in parentheses otherwise the command processor generates the error below. It seems like 0..$a.length-1 is expanded into an range and the length method is called for the last number. The error seems a bit unexpected because I would have thought that the binary minus operator had a higher precedence than the range operator. Also, the number converted into System.Object, doesn't have a length method, and PowerShell would have reported that error instead of an error about the op_Subtraction method.

> write-host $a[0..$a.length-1]
Method invocation failed because [System.Object[]] doesn't contain a method named 'op_Subtraction'.
At line:1 char:28
+ write-host $a[0..$a.length-1 <<<< ]
>> write-host 0..$a.length-1
0..0 1 2 3 4 5.length-1

Get a Reversed Slice

We can also get a reversed slice of the array by specifying the right index before the left index in the range.

> Write-Host $a[5..2]
5 4 3 2
> Write-Host $a[($a.length-1)..0]

We know that -1 represents the last item, so can we simplify the second statement?

> Write-Host $a[-1..0]
5 0

What went wrong? How does PowerShell interpret -1..0?

> -1..0
-1
0
> Write-Host $a[-1] $a[0]
5 0

The range operator expanded -1..0 to 5 and 0. Is there another way specify a reverse slice of the original array? Let's try the following line of reasoning.

  1. From first section, $a.length-x refers to the same item as -x
  2. Replace x with $a.length
  3. So $a.length-$a.length (or 0) refers to the same item as -$a.length
> Write-Host (-1..-$a.length)
-1 -2 -3 -4 -5 -6
> Write-Host $a[-1..-$a.length]
5 4 3 2 1 0

Combining Numeric and Range Indices

Since an array slice can be specified by either a list of numbers or a range, can we combine them?

> Write-Host $a[0,3,2..5]
Cannot convert "System.Object[]" to "System.Int32".
At line:1 char:22
+ Write-Host $a[0,3,2..5 <<<< ]
> 0,3,2..5
Cannot convert "System.Object[]" to "System.Int32".
At line:1 char:8
+ 0,3,2..5 <<<<
> Write-Host 0,3,(2..5)
0 3 2 3 4 5
> Write-Host $a[0,3,(2..5)]
0 3

In the first and second statements, the comma array constructor operator has higher precedence than the range operator, so PowerShell could not create the list from 2..5. In the third statement, we add parentheses to modify the precedence so that the range operator creates a list 2 3 4 5.

However, in the fourth statement, it seems like PowerShell has ignored the list generated by the range operator because only two items are displayed. You can combine a range with a list of indices using the + (Plus) operator. The syntax is a little inconsistent because you can produce an index list without this operator:

> Write-Host $a[0,3+2..5]
0 3 2 3 4 5

Furthermore, you have to use the Plus operator before and after a range:

> Write-Host $a[0,3+2..5,1]
Cannot convert "System.Object[]" to "System.Int32".
At line:1 char:22
+ write-host $n[0,3+2..5 <<<< ,1]
> write-host $n[0,3+2..5+1,4]
0 3 2 3 4 5 1 4

Conclusion

This article has demonstrated how to slice arrays in PowerShell, starting from single item slices, progressing to multiple items, using the range operator and described two approaches to obtain a reversed list of items from an array. We also found that you have to use the Plus operator to combine the comma array constructor operator and range operator to get an array slice.