20

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'))
Anthony Mastrean
  • 21,850
  • 21
  • 110
  • 188

2 Answers2

28

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'
Anthony Mastrean
  • 21,850
  • 21
  • 110
  • 188
  • 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](https://github.com/PowerShell/PowerShell/issues/14732) – mklement0 Jul 09 '22 at 21:12
  • 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. – zett42 Aug 28 '22 at 11:51
  • 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') )` – zett42 Aug 28 '22 at 11:56
  • Using powershell core, if my arrays are typed coming into a function, it's simply `[Linq.Enumerable]::Zip($arr1, $arr2)` – dudeNumber4 May 12 '23 at 20:52
4
# 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