在PowerShell中平整阵列

lokaqttq  于 2022-12-23  发布在  Shell
关注(0)|答案(5)|浏览(116)

假设我们有:

$a = @(1, @(2, @(3)))

我想将$a展平以得到@(1, 2, 3)
我找到了one solution

@($a | % {$_}).count

但也许有更优雅的方式?

x7yiwoj4

x7yiwoj41#

管道化是使嵌套结构扁平化的正确方法,所以我不确定什么会“优雅”。是的,语法看起来有点行噪声,但坦率地说相当有用。

2020年编辑

现在推荐的语法是将%扩展为ForEach-Object,这有点冗长,但可读性更强:

@($a | ForEach-Object {$_}).count
wgmfuz8q

wgmfuz8q2#

相同的代码,只是 Package 在函数中:

function Flatten($a)
{
    ,@($a | % {$_})
}

测试:

function AssertLength($expectedLength, $arr)
{
    if($ExpectedLength -eq $arr.length) 
    {
        Write-Host "OK"
    }
    else 
    {
        Write-Host "FAILURE"
    }
}

# Tests
AssertLength 0 (Flatten @())
AssertLength 1 (Flatten 1)
AssertLength 1 (Flatten @(1))
AssertLength 2 (Flatten @(1, 2))
AssertLength 2 (Flatten @(1, @(2)))
AssertLength 3 (Flatten @(1, @(2, @(3))))
toe95027

toe950273#

Powershell v4.0中引入的.ForEach()数组方法可能最完美地解决了这个问题。从性能方面来说,它的优点是不需要构造管道,因此在某些情况下,它的性能可能更好。

> $a.ForEach({$_}).Count
3

如果你已经有了一个管道,扁平化数组最简单的方法就是通过Write-Output管道:

> $b = $a | Write-Output
> $b.Count
3
wwtsj6pe

wwtsj6pe4#

有一些 * 嵌套数组 * 的例子,其中管道到ForEach-Object根本无法处理它们。
例如,给定嵌套数组:

$toUnroll = @(@(0,1),@(2,3),@(@(4,@(5,6)),@(7,8),9),10)

如果我们尝试通过管道连接到ForEach-Object,结果将是:

PS /> $toUnroll | ForEach-Object { $_ }

0
1
2
3
4

Length         : 2
LongLength     : 2
Rank           : 1
SyncRoot       : {5, 6}
IsReadOnly     : False
IsFixedSize    : True
IsSynchronized : False
Count          : 2

7
8
9
10

Write-Output也不能处理展开:

$toUnroll | Write-Output | ForEach-Object GetType

IsPublic IsSerial Name             BaseType
-------- -------- ----             --------
True     True     Int32            System.ValueType
True     True     Int32            System.ValueType
True     True     Int32            System.ValueType
True     True     Int32            System.ValueType
True     True     Int32            System.ValueType
True     True     Object[]         System.Array
True     True     Int32            System.ValueType
True     True     Int32            System.ValueType
True     True     Int32            System.ValueType
True     True     Int32            System.ValueType

下面我们可以看到3个不同的例子,关于我们如何处理这些 * 嵌套数组 * 的扁平化,包括一个 * 单行anonymous function*。

*递归Function

此技术按顺序展开数组。

function RecursiveUnroll {
    [cmdletbinding()]
    param(
        [parameter(Mandatory, ValueFromPipeline)]
        [object[]] $Unroll
    )

    process {
        foreach($item in $Unroll) {
            if($item -is [object[]]) {
                RecursiveUnroll -Unroll $item
                continue
            }
            $item
        }
    }
}

RecursiveUnroll -Unroll $toUnroll
# Results in an array from 0 to 10

*单行匿名函数:

这个 * 脚本块 * 的逻辑与上面演示的函数完全相同。

$toUnroll | & { process { if($_ -is [object[]]) { return $_ | & $MyInvocation.MyCommand.ScriptBlock }; $_ }}

**递归Class方法(可以是 * 静态 * 或 * 示例

和递归的function例子一样,我们可以期望数组保持它的顺序,我们可以补充一下,这种技术应该比function,因为方法的参数绑定要快得多。

class Unroller {
    [object[]] $Array

    Unroller() { }
    Unroller([object[]] $Array) {
        $this.Array = $Array
    }

    static [object] Unroll([object[]] $Array) {
        $result = foreach($item in $Array) {
            if($item -is [object[]]) {
                [Unroller]::Unroll($item)
                continue
            }
            $item
        }
        return $result
    }
    [object] Unroll () {
        return [Unroller]::Unroll($this.Array)
    }
}

# Instantiating and using using the instance method of our class:
$instance = [Unroller] $toUnroll
$instance.Unroll()
# Results in an array from 0 to 10

# Using the static method of our class, no need to instantiate:
[Unroller]::Unroll($toUnroll)
# Results in an array from 0 to 10

这种技术应该是最快的一种,缺点是我们不能期望有序数组

$queue = [Collections.Queue]::new()
$queue.Enqueue($toUnroll)

while($queue.Count) {
    foreach($item in $queue.Dequeue()) {
        if($item -is [object[]]) {
            $queue.Enqueue($item)
            continue
        }
        $item
    }
}

# Using our given nested array as an example we can expect
# a flattened array with the following order:
# 10, 0, 1, 2, 3, 9, 4, 7, 8, 5, 6

最后,使用Stack可以保证顺序的保持,这种技术也是非常有效的。

$stack = [Collections.Stack]::new()
$stack.Push($toUnroll)

$result = while($stack.Count) {
    foreach($item in $stack.Pop()) {
        if($item -is [object[]]) {
            [array]::Reverse($item)
            $stack.Push($item)
            continue
        }
        $item
    }
}

[array]::Reverse($result)
$result
uajslkp6

uajslkp65#

您可以使用.NET的String.Join方法。

[String]::Join("",$array)

相关问题