Justin
Justin

Reputation: 154

How to combine every element in an array with every element in multiple other arrays in Powershell

In Powershell, I am trying to combine the elements of several arrays to create an array of unique strings. I need to combine every element from each array with every element in the other arrays. It is difficult to concisely explain what I mean, so it may be easier to show.

I start with a 2d-array that looks something like this:

$array = @('this', ('A','B','C'), ('1','2','3','4'), 'that')

I need to create an array, whose contents will look like this:

thisA1that
thisA2that
thisA3that
thisA4that
thisB1that
thisB2that
thisB3that
thisB4that
thisC1that
thisC2that
thisC3that
thisC4that

The length and number of arrays in the original array are variable, and I don't know the order of the items in the original array.

So far, I've tried a few methods, but my logic has been wrong. Here was my first attempt at a solution:

$tempList = @()

#get total number of resources that will be created
$total = 1
for($i=0; $i -lt $array.Count; $i++){$total = $total * $array[$i].Count}

# build each resource from permutations of array parts
for ($i = 0; $i -lt $array.Count; $i++)
{

    for ($j = 0; $j -lt $total; $j++)
    {
        $tempList += @('')
        $idx = $total % $array[$i].Count
        # item may either be string or an array. If string, just add it. If array, add the item at the index
        if ($array[$i] -is [array]){ $tempList[$j] += $array[$i][$idx] }
        else { $tempList[$j] += $array[$i] }
    }

}

In this example, my logic with the modulus operator was wrong, so it would grab the only the first index of each array every time. Upon further consideration, even if I fix the modulus logic, the overall logic would still be wrong. For example, if the second two arrays were the same size, I would get 'A' paired with '1' each time, 'B' with '2', etc.

I'm sure there is a way to do this, but I simply can't seem to see it.

Upvotes: 2

Views: 1022

Answers (2)

JohnLBevan
JohnLBevan

Reputation: 24410

I think the answer's to use recursion so you can handle the fact that the array of arrays can be any length. This recursive function should:

  • take the first array from the array-of-arrays
  • loop through each item in that first array
  • if the first array is also the last array, just return each item.
  • if there are more arrays in the array-of-arrays then pass the remaining arrays to the recursive function
  • for each result from the recursive function, prefix the return value with the current item from the first array.

I think the code explains itself better than the above:

function Combine-Array {
    [CmdletBinding()]
    Param (
        [string[][]]$Array
    )
    Process {
        $current = $Array[0]
        foreach ($item in $current) {
            if ($Array.Count -gt 1) {
                Combine-Array ([string[][]]@($Array | Select -Skip 1)) | %{'{0}{1}' -f $item, $_}
            } else {
                $item
            }
        }
    }
}

Combine-Array  @('this', ('A','B','C'), ('1','2','3','4'), 'that')

Upvotes: 4

user4003407
user4003407

Reputation: 22102

You can write pipeline function, which would add one more subarray into the mix. And then you call it as many times as many subarrays you have:

function CartesianProduct {
    param(
        [array] $a
    )
    filter f {
        $i = $_
        $a[$MyInvocation.PipelinePosition - 1] | ForEach-Object { $i + $_ }
    }
    Invoke-Expression ('''''' + ' | f' * $a.Length)
}

CartesianProduct ('this', ('A','B','C'), ('1','2','3','4'), 'that')

Upvotes: 3

Related Questions