programing

PowerShell에서 배열 평탄화

muds 2023. 10. 8. 10:23
반응형

PowerShell에서 배열 평탄화

다음이 있다고 가정합니다.

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

평평하게 하고 싶습니다.$a갖기 위해@(1, 2, 3).

가지 해결책을 찾았습니다.

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

하지만 좀 더 우아한 방법이 있을까요?

배관은 중첩된 구조물을 평평하게 만드는 올바른 방법이므로, 무엇이 "우아한" 것인지 잘 모르겠습니다.네, 구문이 약간 선소리가 나지만, 솔직히 꽤 유용합니다.

2020 편집

요즘 권장되는 구문은 확장하는 것입니다.%로.ForEach-Object. 좀 더 장황하지만 확실히 더 읽기 쉽습니다.

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

동일한 코드, 기능으로 랩핑:

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))))

경고:마지막에 편집 보기!

이 문제는 Powershell v4.0에 도입된 어레이 방식을 통해 가장 부드럽게 해결될 것입니다.성능 측면에서는 파이프라인을 구성할 필요가 없으므로 경우에 따라 성능이 더 우수할 수 있다는 장점이 있습니다.

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

파이프라인이 이미 있는 경우 배열을 평평하게 만드는 가장 쉬운 방법은 파이프라인을 관통하는 것입니다.Write-Output:

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

--

편집: 위의 답변은 사실 정확하지 않습니다.중첩된 배열이 여러 개인 배열을 완전히 평탄화하지는 않습니다.@SantiagoSquarzon의 답변에는 여러 개의 언롤이 필요한 깊이 중첩된 배열의 예가 있습니다.

> $toUnroll = @(@(0,1),@(2,3),@(@(4,@(5,6)),@(7,8),9),10) # 11 elements
> $toUnroll.ForEach({$_}).Count
8

> $toUnroll.ForEach({$_}).ForEach({$_}).Count
10

> $toUnroll.ForEach({$_}).ForEach({$_}).ForEach({$_}).Count
11

아니면, 더 명확하게:

> $toUnroll = @(@(0,1),@(2,3),@(@(4,@(5,6)),@(7,8),9),10) # 11 elements
### Unroll 0 times
> $toUnroll.ForEach({$_ | ConvertTo-Json -Compress})
[0,1]
[2,3]
[[4,[5,6]],[7,8],9]
10

### Unroll 1 times
> $toUnroll.ForEach({$_}).ForEach({$_ | ConvertTo-Json -Compress}) 
0
1
2
3
[4,[5,6]]
[7,8]
9
10

### Unroll 2 times
> $toUnroll.ForEach({$_}).ForEach({$_}).ForEach({$_ | ConvertTo-Json -Compress})
0
1
2
3
4
[5,6]
7
8
9
10

### Unroll 3 times
> $toUnroll.ForEach({$_}).ForEach({$_}).ForEach({$_}).ForEach({$_ | ConvertTo-Json -Compress})
0
1
2
3
4
5
6
7
8
9
10

다음으로 파이핑하는 중첩 배열의 예가 있습니다.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

아래에서는 한 줄짜리 익명 함수를 포함하여 이러한 중첩 배열의 평탄화를 처리하는 방법에 대한 몇 가지 예를 볼 수 있습니다.

이 기술은 배열을 순서대로 풉니다.

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 }; $_ }}
  • 재귀 클래스 메서드(정적이거나 인스턴스일 수 있음)

재귀 함수 예제와 마찬가지로 배열이 순서를 유지하는 것을 기대할 수 있습니다.반복 기능 또는 스크립트 블록 호출은 비용이 많이 들기 때문에 이 기법은 재귀적 기능 접근 방식보다 빠르며, 이는 PowerShell 스크립팅 성능 고려 사항에 명시되어 있습니다.

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 = [System.Collections.Generic.Queue[object]]::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 = [System.Collections.Generic.Stack[object]]::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 # Should be array from 0 to 10

사용하시면 됩니다.NET의 String.가입방법.

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

언급URL : https://stackoverflow.com/questions/711991/flatten-array-in-powershell

반응형