我想根据变量将单个数组转换为一组较小的数组。因此,当大小为 3 时,
0,1,2,3,4,5,6,7,8,9
将变为 0,1,2
,3,4,5
,6,7,8
,9
。
我目前的做法:
$ids=@(0,1,2,3,4,5,6,7,8,9)
$size=3
0..[math]::Round($ids.count/$size) | % {
# slice first elements
$x = $ids[0..($size-1)]
# redefine array w/ remaining values
$ids = $ids[$size..$ids.Length]
# return elements (as an array, which isn't happening)
$x
} | % { "IDS: $($_ -Join ",")" }
产品:
IDS: 0
IDS: 1
IDS: 2
IDS: 3
IDS: 4
IDS: 5
IDS: 6
IDS: 7
IDS: 8
IDS: 9
我希望是:
IDS: 0,1,2
IDS: 3,4,5
IDS: 6,7,8
IDS: 9
我错过了什么?
为了完整起见:
function Slice-Array
{
[CmdletBinding()]
param (
[Parameter(Mandatory=$true, Position=0, ValueFromPipeline=$True)]
[String[]]$Item,
[int]$Size=10
)
BEGIN { $Items=@()}
PROCESS {
foreach ($i in $Item ) { $Items += $i }
}
END {
0..[math]::Floor($Items.count/$Size) | ForEach-Object {
$x, $Items = $Items[0..($Size-1)], $Items[$Size..$Items.Length]; ,$x
}
}
}
用途:
@(0,1,2,3,4,5,6,7,8,9) | Slice-Array -Size 3 | ForEach-Object { "IDs: $($_ -Join ",")" }
cls
$ids=@(0,1,2,3,4,5,6,7,8,9)
$size=3
<#
Manual Selection:
$ids | Select-Object -First 3 -Skip 0
$ids | Select-Object -First 3 -Skip 3
$ids | Select-Object -First 3 -Skip 6
$ids | Select-Object -First 3 -Skip 9
#>
# Select via looping
$idx = 0
while ($($size * $idx) -lt $ids.Length){
$group = $ids | Select-Object -First $size -skip ($size * $idx)
$group -join ","
$idx ++
}
您可以使用
,$x
而不仅仅是 $x
。
文档中的
about_Operators
部分有这样的内容:
, Comma operator
As a binary operator, the comma creates an array. As a unary
operator, the comma creates an array with one member. Place the
comma before the member.
Craig 本人已方便地将分割(分区)功能封装在一个强大的函数中:
让我提供一个性能更好的演变(PSv3+语法,重命名为
Split-Array
),其中:
使用可扩展的
System.Collections.Generic.List[object]]
集合更有效地收集输入对象。
在分割过程中不会修改集合,而是从中提取范围元素。
function Split-Array {
[CmdletBinding()]
param (
[Parameter(Mandatory, ValueFromPipeline)]
[String[]] $InputObject
,
[ValidateRange(1, [int]::MaxValue)]
[int] $Size = 10
)
begin { $items = New-Object System.Collections.Generic.List[object] }
process { $items.AddRange($InputObject) }
end {
$chunkCount = [Math]::Floor($items.Count / $Size)
foreach ($chunkNdx in 0..($chunkCount-1)) {
, $items.GetRange($chunkNdx * $Size, $Size).ToArray()
}
if ($chunkCount * $Size -lt $items.Count) {
, $items.GetRange($chunkCount * $Size, $items.Count - $chunkCount * $Size).ToArray()
}
}
}
对于较小的输入集合,优化不会有太大影响,但一旦进入数千个元素,加速可能会非常惊人:
Time-Command
:
$ids = 0..1e4 # 10,000 numbers
$size = 3 # chunk size
Time-Command { $ids | Split-Array -size $size }, # optimized
{ $ids | Slice-Array -size $size } # original
来自运行 Windows 5.1 的单核 Windows 10 VM 的示例结果(绝对时间并不重要,但因素很重要):
Command Secs (10-run avg.) TimeSpan Factor
------- ------------------ -------- ------
$ids | Split-Array -size $size 0.150 00:00:00.1498207 1.00
$ids | Slice-Array -size $size 10.382 00:00:10.3820590 69.30
注意未优化的函数几乎慢了 70 倍。
为Bill Stewart 的有效解决方案添加解释:
输出集合,例如数组[1],可以隐式或使用
return
通过管道发送其元素单独;也就是说,集合是枚举(展开):
# Count objects received.
PS> (1..3 | Measure-Object).Count
3 # Array elements were sent *individually* through the pipeline.
,
(逗号;数组构造运算符)来防止枚举是一种方便简洁的方法,尽管有些晦涩解决方法:
PS> (, (1..3) | Measure-Object).Count
1 # By wrapping the array in a helper array, the original array was preserved.
也就是说,
, <collection>
在原始集合周围创建一个瞬态单元素辅助数组,以便枚举仅应用于辅助数组,将包含的原始集合按原样输出为单个对象。
Write-Output -NoEnumerate
,它清楚地表明了将集合作为单个对象输出的意图。
PS> (Write-Output -NoEnumerate (1..3) | Measure-Object).Count
1 # Write-Output -NoEnumerate prevented enumeration.
目视检查的陷阱:
输出用于显示时,多个数组之间的边界似乎再次被擦除:
PS> (1..2), (3..4) # Output two arrays without enumeration
1
2
3
4
也就是说,即使两个 2 元素数组均作为单个对象发送,但通过在各自的行上显示每个元素,输出使其看起来像是收到了一个平面 4 元素数组。
解决这个问题的一个简单方法是stringify每个数组,这会将每个数组转换为一个包含空格分隔的元素列表的字符串。
PS> (1..2), (3..4) | ForEach-Object { "$_" }
1 2
3 4
现在很明显收到了两个单独的数组。
[1] 枚举了哪些数据类型:
实现
IEnumerable
接口的数据类型实例会自动枚举,但也有例外:IDictionary
的类型(例如 hashtables)是 not 枚举的,XmlNode
实例也不是。DataTable
的实例(不实现IEnumerable
)被枚举(作为其.Rows
集合的元素) - 请参阅这个答案和源代码。 此外,请注意,
)具有内存使用优势(因为每个项目都是单独处理的),我会将问题更改为: 将 PowerShell
管道函数:
Install-Script -Name Create-Batch
示例1*
1..5 |Create-Batch -Size 2 |ForEach-Object { "$_" }
1 2
3 4
5
*
Get-Process |Create-Batch |Set-Content .\Process.txt
这将创建一个包含所有 itam 的单个批次(数组) 此语句的结果与:
Get-Process |Set-Content .\Process.txt
相同,但请注意,其出现速度(原因尚不清楚)大约是其两倍。
请参阅:
#8270
建议:为 Select-Object 添加分块(分区、批处理)机制,类似于 Get-Content -ReadCount
(.NET 6+) 使事情变得非常简单 Enumerable.Chunk
:
$ids = @(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
$size = 3
[System.Linq.Enumerable]::Chunk[object]($ids, $size) |
ForEach-Object { 'IDS: {0}' -f ($_ -join ',') }
# IDS: 0,1,2
# IDS: 3,4,5
# IDS: 6,7,8
# IDS: 9