24

I want to zip two arrays, like how Ruby does it, but in PowerShell. Here's a hypothetical operator (I know we're probably talking about some kind of goofy pipeline, but I just wanted to show an example output).

PS> @(1, 2, 3) -zip @('A', 'B', 'C')
@(@(1, 'A'), @(2, 'B'), @(3, 'C'))
5
  • 1
    automatedops.com/blog/2017/02/09/… Commented Mar 30, 2017 at 15:23
  • Do you want to keep them as arrays (array of arrays), or just all values as one array? Commented Mar 30, 2017 at 15:23
  • I do NOT want to merge the arrays. I need the elements as pairs for later processing, hopefully in a pipeline. Commented Mar 30, 2017 at 15:26
  • @RogerLipscombe That looks like an answer that is better than rolling your own function Commented Mar 30, 2017 at 15:44
  • 3
    [Linq.Enumerable]::Zip((1, 2, 3), ('A', 'B', 'C'), [Func[Object, Object, Object[]]]{,$args}) Commented Mar 30, 2017 at 15:45

2 Answers 2

31

There's nothing built-in and it's probably not recommended to "roll your own" function. So, we'll take the LINQ Zip method and wrap it up.

[System.Linq.Enumerable]::Zip((1, 2, 3), ('A', 'B', 'C'), [Func[Object, Object, Object[]]]{ ,$args })

That works, but it's not pleasant to work with over time. We can wrap the function (and include that in our profile?).

function Select-Zip {
    [CmdletBinding()]
    Param(
        $First,
        $Second,
        $ResultSelector = { ,$args }
    )

    [System.Linq.Enumerable]::Zip($First, $Second, [Func[Object, Object, Object[]]]$ResultSelector)
}

And you can call it simply:

PS> Select-Zip -First 1, 2, 3 -Second 'A', 'B', 'C'
1
A
2
B
3
C

With a non-default result selector:

PS> Select-Zip -First 1, 2, 3 -Second 'A', 'B', 'C' -ResultSelector { param($a, $b) "$a::$b" }
1::A
2::B
3::C

I leave the pipeline version as an exercise to the astute reader :)

PS> 1, 2, 3 | Select-Zip -Second 'A', 'B', 'C'
Sign up to request clarification or add additional context in comments.

4 Comments

Nicely done. As for a hypothetical operator: while not an operator, an enhancement was proposed to the foreach statement, along the lines of foreach ($elementFromArray1, $elementFromArray2 in @(1, 2, 3), @('A', 'B', 'C')) { ... } - unfortunately, it was declined - see GitHub issue #14732
With PS 7.x we can also use the Linq.Enumerable.Zip overload that outputs tuples, alleviating the need to pass a function: [Linq.Enumerable]::Zip( [int[]](1, 2, 3), [string[]]('A', 'B', 'C')). Note that the arrays must be typed because otherwise PowerShell can't find the correct overload.
If you don't know the types of the arrays beforehand, casting to [object[]] arrays also works: [Linq.Enumerable]::Zip( [object[]](1, 2, 3), [object[]]('A', 'B', 'C') )
Using powershell core, if my arrays are typed coming into a function, it's simply [Linq.Enumerable]::Zip($arr1, $arr2)
6
# Multiple arrays
$Sizes = @("small", "large", "tiny")
$Colors = @("red", "green", "blue")
$Shapes = @("circle", "square", "oval")

# The first line "zips"
$Sizes | ForEach-Object { $i = 0 }{ @{size=$_; color=$Colors[$i]; shape=$Shapes[$i]}; $i++ } `
 | ForEach-Object { $_.size + " " + $_.color + " " + $_.shape }

Outputs:

small red circle
large green square
tiny blue oval

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.