将PowerShell阵列划分为多组较小的阵列

6qqygrtg  于 2023-02-23  发布在  Shell
关注(0)|答案(6)|浏览(147)

我想把一个数组转换成一组更小的数组,基于一个变量,所以当大小为3时,0,1,2,3,4,5,6,7,8,9将变成0,1,23,4,56,7,89
我目前的做法:

$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

我错过了什么?

toe95027

toe950271#

为完整起见:

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 ",")" }
w6lpcovy

w6lpcovy2#

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 ++
}
hgqdbh6s

hgqdbh6s3#

您可以使用,$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.
vyswwuz2

vyswwuz24#

克雷格自己已经方便地将拆分(分区)功能封装在一个**robust function中:
让我提供一个
性能更好的改进**(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虚拟机的示例结果(绝对时间并不重要,但因素是):

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

stszievb

stszievb5#

要向Bill Stewart's effective solution添加 * 说明 *:

**隐式 * 或使用return输出数组[1]之类的集合时,会通过pipeline****分别发送其元素;也就是说,集合是 enumerated(展开的):

# 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>在原始集合周围创建一个临时的单元素helper数组,以便枚举只应用于helper数组,并将包含的原始集合按原样输出为单个对象。
一个概念上更清晰,但更冗长和 * 更慢 * 的方法是使用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,* 不 * 枚举,XmlNode示例也不枚举。
相反,DataTable的示例(不实现IEnumerable)* 被 * 枚举(作为它们的.Rows集合的元素)-参见this answer和源代码。
此外,请注意,external program 的stdout输出是 * 逐行 * 枚举的。

46scxncf

46scxncf6#

由于PowerShell“数组”通常在[pipeline]中展开,并且使用pipeline(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 Suggestion: Add a chunking (partitioning, batching) mechanism to Select-Object, analogous to Get-Content -ReadCount

相关问题