ezamur
ezamur

Reputation: 2182

Scala combination of elements of arbitrary number of collections

Let's say there are three collections:

val numbers = List("1", "2")
val signs = List("-", "+")
val chars = List("a", "b")

I want to generate combinations of elements of those collections. What I want is not exactly a cartesian product, nor all possible combinations. What I want to have is something like this:

(1)
(1, -)
(1, -, a)
(1, -, b)
(1, +)
(1, +, a)
(1, +, b)
...

If I could sum this up in a set of formulas, I want to have these sets:

numbers
signs
chars
numbers * signs
numbers * chars
signs * chars
numbers * signs * chars

with important note that each of the product can contain only one element from each of the sets. These tuples, for example, would not be something I want in my result:

(1, 2, -)
(a, -, +)

because they have two numbers or two signs.

Any hints on how I could approach this interesting problem? I think Python package itertools has product function that deals with this, but I could not find anything similar for Scala.

Upvotes: 1

Views: 355

Answers (4)

ezamur
ezamur

Reputation: 2182

Thanks everyone for hints, I managed to solve the issue. This is how I did it...

First I defined a set of collection names, let's call them that way:

val set: Set[String] = Set("numbers", "signs", "chars")

In addition to that, I defined their values:

val valueMap: Map[String, List[String]] = Map("numbers" -> List("1", "2", "3"), "signs" -> List("+", "-"), "chars" -> List("a", "b")

Then I did some mapping and magic:

val kpiComboPhase1 = set.subsets.toList.map(aSet => {
  aSet.toList.flatMap(el => valueMap.get(el))
})
val kpiComboPhase2 = kpiComboPhase1.map(l => {
  if (l.length === 1) l.flatten else l
})

This helped me to get something like this:

Set()
Set(numbers)
Set(signs)
Set(chars)
Set(numbers, signs)
Set(numbers, chars)
Set(signs, chars)
Set(numbers, signs, chars)

After that, I used values of each of those sets from valueMap (each value is List[String] and applied this approach https://stackoverflow.com/a/42095955/589571 to make a recursive cross product of arbitrary number of lists. I needed little more of mapping and gymnastics to get me the structure I wanted to have but in general, this was it.

Upvotes: 0

jwvh
jwvh

Reputation: 51271

One problem with your request is that tuples of different sizes are actually different types, so you don't want to mix them in a collection.

Using a List[List[String]] to express the result, I think this gets at what you want.

val numbers = List("1", "2")
val signs = List("-", "+")
val chars = List("a", "b")

numbers.flatMap{n =>
  List(n) :: signs.flatMap{s =>
    List(s) :: List(n,s) :: chars.flatMap{c =>
      List(List(c), List(n,c), List(s,c), List(n,s,c))
    }
  }
}.distinct

Upvotes: 0

nicodp
nicodp

Reputation: 2392

I guess what you want is all possible subsets of elements from those collections, in order and not repeating. You can do something alike:

val res: List[List[String]] = (for (x <- numbers) yield List(x)) ++
  (for { x <- numbers; y <- signs } yield List(x,y)) ++
    (for { x <- numbers; y <- signs; z <- chars } yield List(x, y, z))

Basically, it's a mixing of @jwvh and @Dima's answers. If you want to obtain tuples instead of lists, you can do:

res.map(s => s match {
  case List(c) => (c)
  case List(x, y) => (x, y)
  case List(x,y,z) => (x,y,z)
  case _ => (s)
})

The output:

scala> res.map(s => s match { case List(c) => (c); case List(x, y) =>
(x,y); case List(x,y,z) => (x,y,z); case _ => (s) })
res21: List[java.io.Serializable] = List(1, 2, (1,-), (1,+), (2,-), (2,+),
(1,-,a), (1,-,b), (1,+,a), (1,+,b), (2,-,a), (2,-,b), (2,+,a), (2,+,b))

Recall that this solution is very specific to your problem.

Upvotes: 1

Dima
Dima

Reputation: 40500

"Product" in scala would look something like this:

 for {
  n <- numbers
  s <- signs
  c <- chars
 } yield (n, s, c)

That should help you get started hopefully.

Upvotes: 0

Related Questions