相对于数组的两端切片数组的惯用方法是什么?

What is the idiomatic way to slice an array relative to both of its ends?

Powershell 的数组表示法在对数组末尾进行切片时有相当奇怪的行为,尽管有记录。 the official documentation 中的这一部分很好地总结了怪异之处:

Negative numbers count from the end of the array. For example, "-1" refers to the last element of the array. To display the last three elements of the array, type:

$a[-3..-1]

However, be cautious when using this notation.

$a[0..-2]

This command does not refer to all the elements of the array, except for the last one. It refers to the first, last, and second-to-last elements in the array.

以下代码证实了这一奇异之处:

$a = 0,1,2,3
$a[1..-1]

这确实输出了这个奇怪的结果:

1
0
3

那么,问题是,用一个索引相对于数组的开头而另一个索引相对于数组的结尾进行切片的惯用方法是什么?

请告诉我它比这个丑陋的烂摊子更好:

$a[1..($a.Count-1)]

编辑:

另一种描述我正在寻找的方法是:这个 python 表达式的惯用 Powershell 等价物:

a=1,2,3,4
a[1:-1]

当然,计算结果为 (2,3)

如果你想从数组的末尾获取 n 个元素,只需从 -n-1:

PS C:\> $a = 0,1,2,3
PS C:\> $n = 2
PS C:\> $a[-$n..-1]
2
3

编辑: PowerShell 不支持相对于数组开头 结尾的索引,因为 $a[$i..$j] 有效。在 Python 表达式 a[i:j] 中,您将 ij 分别指定为第一个和最后一个索引。但是,在 PowerShell 中 ..range operator,它会生成一个数字序列。在表达式 $a[$i..$j] 中,解释器首先将 $i..$j 计算为整数列表,然后使用该列表检索这些索引上的数组元素:

PS C:\> $a = 0,1,2,3
PS C:\> $i = 1; $j = -1
PS C:\> $index = $i..$j
PS C:\> $index
1
0
-1
PS C:\> $a[$index]
1
0
3

如果您需要模仿 Python 的行为,您必须 使用子表达式:

PS C:\> $a = 0,1,2,3
PS C:\> $i = 1; $j = -1
PS C:\> $a[$i..($a.Length+$j-1)]
1
2

如果您要查找数组中的前三个和最后三个元素,结果在一个数组中,只需添加一点数组即可满足需要。

[array]$A = (([int][char]'A')..([int][char]'Z')) | ForEach-Object {[char]$_}
$B = $A[0..2]+$A[-3..-1]
Clear-Host
Write-Host "Original List"
Write-Host $A -NoNewline -Separator ', '
Write-Host
Write-Host "First three and last three"
Write-Host $B -NoNewline -Separator ', '

产量:

Original List
A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z
First three and last three
A, B, C, X, Y, Z

这可能是对数组两端进行切片的最惯用的方法:

$array[start..stop] 其中 stop 是通过将数组的长度减去从数组末尾偏移的值来定义的:

$a = 1,2,3,4,5,6,7,8,9
$start = 2
$stop = $a.Length-3
$a[$start..$stop]

这将 return 3 4 5 6 7

起始值从零开始计数,因此起始值“2”表示数组的第三个元素。停止值是用$a.Length-3计算的,这将丢弃最后两个值,因为$a.Length-3本身包含在切片中。

为了清楚起见,我定义了$start和$stop,显然你也可以这样写:

$a = 1,2,3,4,5,6,7,8,9
$a[2..($a.Length-3)]

这也将 return 3 4 5 6 7

虽然没有您想要的那么整洁,但 PowerShell 的工作方式更简洁...

(@(1,2,3,4)) | Select-Object -Skip 1

returns ...

2
3
4

我相信这是正确的做法。所有其他方法都需要更多代码。

$a[1..($a.Count-1)]

此外,如果将数组转换为字符串,则可以轻松获取如下数据:

$a = 0,1,2,3
[string]::Concat($a).Substring(1)
$arr = @(10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

$arr | Select-Object -First 5 | Select-Object -Index (@(0..4) | Where-Object { $_ % 2 -eq 0}) 
$arr | Select-Object -Last 5
$arr | Select-Object -Unique
$arr | Sort-Object | Select-Object -Unique
$arr | Where-Object {  $_ % 5 -eq 0 } | Sort-Object | Select-Object -Unique
$arr | Select-Object -First ($arr.Count - 3)

其实代码不言自明。 我的活动不需要解释。

然而,

  1. 提供前五个元素,以及这五个元素的每一秒。等于 Python
  2. 中的 arr[:5:2]
  3. 获取最后五个元素。
  4. 提供独特的元素
  5. 先排序后提供唯一性
  6. 通过应用 5 的模数、排序、唯一性仅给出等于 0 的元素。
  7. 提供该数组中元素的第一个计数减去三个元素。

合并 Select-Object -SkipSelect-Object -SkipLast 如:

$a = 0,1,2,3
$a | Select-Object -Skip 1 | Select-Object -SkipLast 1

Returns:

1
2

不如 Python 优雅,但至少你不必使用 CountLength,这意味着如果数组不存储在变量。

不幸的是,将您的数组通过管道传输到 Select-Object -Skip $skipStart | Select-Object -SkipLast $skipEnd 是获取正确项目的唯一万无一失的惯用方法。

(@wensveen 通过这个策略率先到达那里,但是现在还不允许我发表评论,我认为给出解释保证了完整回复的细节。)

计算范围不起作用

如果您的列表比您预期的要短,则 $a[3..$a.count-3] 等范围将不起作用:假设 $a 有 4 个项目,您最终得到的范围是 3..1,即 @(3, 2, 1),三个项目的顺序相反,而在 Python 示例中,a[3:-3] 将 return 零元素,因为升序范围从头开始三个末尾三个是空的。 (在 Python 中,范围顺序是一个额外的显式参数,a[-1:1:-1] 允许 reverse-ordered 结果。)

输出格式

按照惯例,如果需要强制输出为数组,可以将管道包装在 @(...) 数组强制转换中。 @RiverHeart 提到想要保留输入数组类型,例如Int32[],但据我所知这对普通索引也不起作用,我可能遗漏了一些东西。

获取正确的项目

另一个简短的提及:您使用的跳过操作的数字与范围符号略有不同。如果您从索引 1 开始,那么您也会从 zero-indexed 数组的开头跳过 1 个项目,所以两者是一样的;但在末尾跳过 1 相当于索引 -2,或者 $a.count-2,对于 second-last 元素。

添加数组索引

最后,将范围文字加在一起是一个不错的功能。由于上述原因,它不会一概而论,它不会阻止您意外地获得相同的项目两次,比如说,如果您的范围重叠,但是能够在交互式提示下进入 $a[0..2+-3..-1] 以获取前两个和最后三个,比制作和组合单独的数组更简洁,而且仍然很清楚。我想如果你想对 Python 嗤之以鼻,那是它做的不那么简单的一件事!