-3

I am a newbie in Scala and would appreciate any direction or help in solving the following problem.

Input

I have a Map[String, Array[Double]] that looks like follow:

Map(foo -> Array(12, 25, 100), bar -> Array(0.1, 0.001))

The Map can contain between 1 and 10 keys (depends on the some parameters in my application).

Processing

I would like to apply a cartesian product between the arrays of all keys and generate a structure that contains all the possible combinations of all values of all arrays.

In the example above, the cartesian product will create 3x2=6 different combinations: (12, 0.1), (12, 0.001), (25, 0.1), (25, 0.01), (100,0.1) and (100, 0.01).

For the sake of another example, in some cases I might have three key: the first key has an Array of 4 values, the second has an Array of 5 values and the third has an Array of 3 values, in this case the product has to generate 4x5x3=60 different combinations.

Desired output

Something like:

Map(config1 -> (foo -> 12, bar -> 0.1), config2 -> (foo -> 12, bar -> 0.001), config3 -> (foo -> 25, bar -> 0.1), config4 -> (foo -> 25, bar -> 0.001), config5 -> (foo -> 100, bar -> 0.1), config6 -> (foo -> 100, bar -> 0.001))
Rami
  • 8,044
  • 18
  • 66
  • 108

2 Answers2

1

You can use a for comprehension to create a cartesian product of two lists, arrays, ...

val start = Map(
  'foo -> Array(12, 25, 100), 
  'bar -> Array(0.1, 0.001), 
  'baz -> Array(2))

// transform arrays into lists with values paired with map key
val pairedWithKey = start.map { case (k,v) => v.map(i => k -> i).toList }

val accumulator = pairedWithKey.head.map(x => Vector(x))
val cartesianProd = pairedWithKey.tail.foldLeft(accumulator)( (acc, elem) => 
  for { x <- acc; y <- elem } yield x :+ y 
)

cartesianProd foreach println
// Vector(('foo,12), ('bar,0.1), ('baz,2))
// Vector(('foo,12), ('bar,0.001), ('baz,2))
// Vector(('foo,25), ('bar,0.1), ('baz,2))
// Vector(('foo,25), ('bar,0.001), ('baz,2))
// Vector(('foo,100), ('bar,0.1), ('baz,2))
// Vector(('foo,100), ('bar,0.001), ('baz,2))

You might want to add some checks before using head and tail.

Peter Neyens
  • 9,770
  • 27
  • 33
1

Since the number of Arrays is dynamic, there is no way you can get tuples as the result.

You can, however, utilize recursion for your purpose:

def process(a: Map[String, Seq[Double]]) = {
    def product(a: List[(String, Seq[Double])]): Seq[List[(String, Double)]] =
        a match {
            case (name, values) :: tail =>
                for {
                    result <- product(tail)
                    value <- values
                } yield (name, value) :: result

            case Nil => Seq(List())
        }

    product(a.toList)
}


val a = Map("foo" -> List(12.0, 25.0, 100.0), "bar" -> List(0.1, 0.001))
println(process(a))

Which gives a result of:

List(List((foo,12.0), (bar,0.1)), List((foo,25.0), (bar,0.1)), List((foo,100.0), (bar,0.1)), List((foo,12.0), (bar,0.001)), List((foo,25.0), (bar,0.001)), List((foo,100.0), (bar,0.001)))
Rok Kralj
  • 46,826
  • 10
  • 71
  • 80