简体   繁体   中英

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.

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.

"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.

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

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM