1

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.

ezamur
  • 2,064
  • 2
  • 22
  • 39
  • What have you tried so far? Any specific problems with your implementation? – Dima Dec 22 '17 at 17:50
  • I tried several different things but the real problem lies in of my level Scala expertise which is not very high so it is nothing worth mentioning here. – ezamur Dec 22 '17 at 19:26
  • 1
    please take a look at https://stackoverflow.com/help/how-to-ask – Dima Dec 22 '17 at 19:33

4 Answers4

1

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.

nicodp
  • 2,362
  • 1
  • 11
  • 20
0

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

Dima
  • 39,570
  • 6
  • 44
  • 70
  • 1
    This would only yield 3-element tuples, but not 2- or single-element ones. A few more `yield`s with limited source lists would do, I think – Alex Savitsky Dec 22 '17 at 18:26
  • @AlexSavitsky Yes, this wasn't meant to be a complete solution of the OP's problem, just an answer to his question of what would be an equivalent of `product` in scala. – Dima Dec 22 '17 at 18:45
  • 1
    I know this wasn't meant to be a complete answer, just pointing out that this is why someone has already downvoted it. At least, maybe, explain to OP what result would he get with that code above (in our case, 3-element tuples). Then he can better apply that code to also solve the 2-element tuples. – Alex Savitsky Dec 22 '17 at 18:52
  • @AlexSavitsky I think, the OP is fully capable of running the code and seeing what result he'd get, even if he cannot infer it from the fact that it is an equivalent of something called `product`. As to "why somebody downvoted" ... You know, if I cared about motives and reasoning of every jerk on internet, I'd probably be already dead from all the stress :D – Dima Dec 22 '17 at 19:32
  • @AlexSavitsky Can you please elaborate and add more details? Sounds like a way to go but I am not sure how it should look like in Scala as I am still very new with it. – ezamur Dec 22 '17 at 22:25
  • @ezamur the answer gives you a way to get all possible combinations of all three types. To get a combination of just two of them, you'll need to declare just two of them in the for-comprehension, and in yield too. You'll need to repeat this for each pair of types you plan to get, then you can combine these lists together. I'm not the author of the answer, so can't really include much code in the comments – Alex Savitsky Dec 22 '17 at 22:34
  • @AlexSavitsky Ok, got it. Thank you. – ezamur Dec 22 '17 at 22:42
0

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
jwvh
  • 50,871
  • 7
  • 38
  • 64
0

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.

ezamur
  • 2,064
  • 2
  • 22
  • 39