I'm trying to emulate MATLAB's 'end
' indexing keyword (such as A[5:end]
) in Powershell but I don't want to type the array name (such as $array
) to access $array.length
for
$array[0..($array.length - 2)]
as discussed in another Stackflow question. I tried $this
(0..7)[4..($this.Length-1)]
given ($this.Length-1)
seems to be interpreted as -1 as the output shows
4
3
2
1
0
7
This makes me think $this is empty when used inside []
indexing an array. Is there a way for an array to refer to itself without explicitly repeating the variable name so I can call the methods of the array to derive the indices? This would be very handy for emulating logical indexing while taking advantage of method chaining (like a.b.c.d[4..end]
).
CodePudding user response:
PowerShell doesn't have any facility for referring to "the collection targeted by this index access operator", but if you want to skip the first N items of a collection/enumerable you can use Select -Skip
:
0..7 |Select -Skip 4
CodePudding user response:
To complement Mathias' helpful answer:
The automatic
$this
variable is not available inside index expressions ([...]
), only in customclass
es (to refer to the instance at hand) and in script blocks acting as .NET event delegates (to refer to the event sender).However, for what you're trying to achieve you don't need a reference to the input array (collection) as a whole: instead, an abstract notation for referring to indices relative to the end of the input array should suffice, and ideally also for "all remaining elements" logic.
You can use negative indices to refer to indices relative to the end of the input array, but that only works with individual indices:
# OK: individual negative indices; get the last and the 3rd last item:
('a', 'b', 'c', 'd')[-3, -1] # -> 'b', 'd'
Unfortunately, because ..
inside an index expression refers to the independent, general-purpose range operator, this does not work for range-based array slicing when negative indices are used as range endpoints:
# !! DOES NOT WORK:
# *Flawed* attempt to get all elements up to and including the 2nd last,
# i.e. to get all elements but the last.
# 0..-2 evaluates to array 0, -1, -2, whose elements then serve as the indices.
('a', 'b', 'c', 'd')[0..-2] # -> !! 'a', 'd', 'c'
That is, the general range operation 0..-2
evaluates to array 0
, -1
, -2
, and the resulting indices are used to extract the elements.
It is this behavior that currently requires an - inconvenient - explicit reference to the array inside the index expression for everything-except-the-last-N-elements logic, such as $array[0..($array.length - 2)]
in your question in order to extract all elements except the last one.
GitHub issue #7940 proposes introducing new syntax that addresses this problem, by effectively implementing C#-style ranges:
While no syntax has been agreed on and no commitment has been made to implement this enhancement, borrowing C#'s syntax directly is an option:
Now | Potential future syntax | Comment |
---|---|---|
$arr[1..($arr.Length-2)] |
$arr[1..^1 ] |
From the 2nd el. through to the next to last. |
$arr[1..($arr.Length-1)] |
$arr[1.. ] |
Everything from the 2nd el. |
$arr[0..9] |
$arr[..9] |
Everything up to the 10th el. |
$arr[-9..-1] |
$arr[^9..] |
Everything from the 9th to last el. |
Note the logic of the from-the-end, 1
-based index syntax (e.g., ^1
refers to the last element) when serving as a range endpoint: It is up-to-but-excluding logic, so that ..^1
means: up to the index before the last one, i.e. the second to last one.
As for the workarounds:
Using Select-Object
with -Skip
/ -SkipLast
is convenient in simple cases, but:
- performs poorly compared to index expressions (
[...]
) (see below) - lacks the flexibility of the latter[1]
- A notable limitation is that you cannot use both
-Skip
and-SkipLast
in a singleSelect-Object
call; GitHub issue #11752 proposes removing this limitation.
- A notable limitation is that you cannot use both
E.g., in the following example (which complements Mathias's -Skip
example), which extracts all elements but the last:
# Get all elements but the last.
$arr | Select-Object -SkipLast 1
Array
$arr
is enumerated, i.e. its elements are sent one by one through the pipeline, a process known as streaming.When captured, the streamed elements are collected in a regular,
[object[]]
-typed PowerShell array, even if the input array is strongly typed - however, this loss of strict typing also applies to extracting multiple elements via[...]
.
Depending on the size of your arrays and the number of slicing operations needed, the performance difference can be significant.
[1] Notably, you can use arbitrary expressions inside [...]
, which is discussed in more detail in this answer.