更新时间:2023-08-19 18:01:58
Theo's helpful answer显示了使用.NET API对已在内存中并存储在变量中的数组进行快速就地排序的方法。
如果希望排序并消除重复项(相当于Sort-Object -Unique
),则可以使用System.Collections.Generic.SortedSet<T>
实例:
PS> [System.Collections.Generic.SortedSet[string]]::new(
[string[]] ('foo', 'bar', 'baz', 'foo'),
[System.StringComparer]::InvariantCultureIgnoreCase
)
bar
baz
foo
注意:如果将[object]
用作SortedSet
的泛型类型参数,则不需要[string[]]
强制转换,但通常***使用特定类型。
注意:结果不是数组,但可以像使用foreach
语句一样在管道中对其进行枚举。将结果复制到数组中-例如,当您需要应用索引时(例如,[0]
)-
预分配相同类型的目标数组并使用.CopyTo()
方法填充它($arr = [string[]]::new($sortedSet.Count); $sortedSet.CopyTo($arr)
)
另一种方法是先对有重复项的数组进行排序,然后再应用System.Linq.Enumerable.Distinct()
:
[string[]] $arr = 'foo', 'bar', 'baz', 'foo'
[Array]::Sort($arr, [System.StringComparer]::InvariantCultureIgnoreCase)
[Linq.Enumerable]::Distinct($arr)
注意:结果是一个惰性可枚举,而不是一个数组,但它可以像使用foreach
语句一样在管道中进行枚举。调用.ToArray()
方法以显式创建数组,例如当您需要应用索引时(例如,[0]
)。
关于您的问题:
那么,-InputObject到底是用来做什么的?
对于多数cmdlet来说,不幸的是,-InputObject
参数只是实现细节:它的目的是通过管道启用输入,并且它直接与数组(集合)一起使用是没有意义的,例如Sort-Object
。
GitHub issue #4242要求清楚地记录-InputObject
,并包含确实有意义地支持直接使用-InputObject
的cmdlet的列表,但不是作为管道输入的替代,而是使用不同的语义,当使用-InputObject
时,将数组(集合)作为一个整体进行操作;例如,1, 'foo' | Get-Member
与Get-Member -InputObject (1, 'foo')
的工作方式(有意义)不同:前者报告数组的元素的类型,而后者报告数组本身的类型。
在数据处理cmdlet(相对于格式化cmdlet)中,实际上只有Write-Output
和Out-String
(部分也是格式化cmdlet)支持直接-InputObject
与数组一起使用;例如:
# Both commands produce the same output.
1, 2 | Write-Output
Write-Output -InputObject 1, 2
即使在那里,嵌套的数组的行为也不同,因为这两种方法的枚举深度不同:
# NOT the same, due to nesting.
1, (2, (3, 4)) | Write-Output # -> 1, 2, (3, 4)
Write-Output -InputObject 1, (2, (3, 4)) # -> 1, (2, (3, 4))
这种不支持直接、数组值的-InputObject
参数是很遗憾的,因为它可以极大地加快速度,特别是在数据已满的情况下:
绕过管道中经常出现的逐个流(需要在每个对象的发送命令和接收命令之间进行某种握手),可以极大地提高性能。
-Value
cmdlet的-Value
参数就是一个例子,接受直接数组值参数而不是管道输入,直接使用-Value
大大加快了操作速度。
# Write 100,000 (1e5) numbers to a file:
# Via the pipeline.
1..1e5 | Set-Content temp.txt
# Via -Value - this is much, much faster.
# E.g. on my macOS machine with PowerShell 7.1, about 100(!) times faster.
Set-Content temp.txt -Value (1..1e5)
潜在改进:
注意:
-InputObject
的数组支持,但这很麻烦,因为需要额外的逻辑,并且(B)需要将参数声明为数组类型(这效率很低,因为即使是通过管道接收的单个对象也包装在数组中),或者将其声明为object
,这可能会丧失类型安全性。理想情况下,PowerShell本身应提供此支持,具体如下:
[Parameter]
属性
具有可以与现有ValueFromPipeline
和ValueFromPipelineByPropertyName
属性组合的新的布尔型EnumerateArgument
属性,当设置为$true
时,将指示PowerShell:
隐式接受指定(标量)参数类型的数组,并直接使用该参数,例如,[int[]]
用于[int]
类型的参数
和枚举这些数组,就像在管道中一样,为每个枚举对象调用cmdlet的process
块(在PowerShell中实现的cmdlet(高级函数))/.ProcessRecord()
方法(二进制cmdlet)。
假设(人为)示例:
function ConvertTo-Long {
[CmdletBinding()]
param(
# WISHFUL THINKING: implicit array support for direct -InputObject arguments
[Parameter(ValueFromPipeline, EnumerateArgument)]
[int] $InputObject
)
process {
[long] $InputObject
}
}
# The following calls would then be equivalent:
1, 2, 3 | ConvertTo-Long
ConvertTo-Long -InputObject 1, 2, 3
注意:
该改进将适用于任何管道绑定参数(不仅仅是-InputObject
)。
可以说,EnumerateArgument
在默认情况下应该是$true
,而那些将数组作为参数传递具有不同含义的罕见cmdlet,例如Get-Member
,应该选择退出--然而,这将是一个向后兼容性问题。
由于建议的增强仍然涉及为每个枚举对象调用process
块/.ProcessRecord()
方法,因此速度不会像自定义实现在一次调用中执行枚举那样显著。然而,对我来说,仅统一管道和直接-InputObject
使用之间的行为的前景就使得这种改进是值得的。