将 PowerShell 数组分割成更小的数组组

问题描述 投票:0回答:7

我想根据变量将单个数组转换为一组较小的数组。因此,当大小为 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

我错过了什么?

arrays powershell data-partitioning
7个回答
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 ",")" }

5
投票
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 ++
} 

4
投票

您可以使用

,$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.

3
投票

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 倍。


1
投票

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
集合的元素) - 请参阅
这个答案源代码 此外,请注意,
外部程序的标准输出输出是逐行枚举的


0
投票
https://learn.microsoft.com/powershell/module/microsoft.powershell.core/about/about_pipelines

)具有内存使用优势(因为每个项目都是单独处理的),我会将问题更改为: 将 PowerShell

管道

切成更小的批次 我为此创建了一个小

Create-Batch

 函数:
Install-Script -Name Create-Batch

示例1

* 1..5 |Create-Batch -Size 2 |ForEach-Object { "$_" } 1 2 3 4 5

示例2

* Get-Process |Create-Batch |Set-Content .\Process.txt

这将创建一个包含所有 itam 的单个批次(数组)
此语句的结果与: 
Get-Process |Set-Content .\Process.txt

相同,但请注意,其出现速度(原因尚不清楚)大约是其两倍。

请参阅:

#8270 建议:为 Select-Object 添加分块(分区、批处理)机制,类似于 Get-Content -ReadCount

    


0
投票
PowerShell 7+

(.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

© www.soinside.com 2019 - 2024. All rights reserved.