10

If I have this:

val a = Array("a ","b ","c ")
val b = Array("x","y")

I would like to know if such a method exists which would let me traverse the first collection, and for each of it's elements, walk the entire second collection. For example, if we take the array a, we would have a,x,a,y,b,x,b,y,c,x,c,y. I know of zip but from what I've seen it only works on collections of the same sizes, and it associates elements from same positions.

Mechanical snail
  • 29,755
  • 14
  • 88
  • 113
Geo
  • 93,257
  • 117
  • 344
  • 520

5 Answers5

26

I'm not sure of a "method", but this can be expressed with just a nested/compound for:

val a = Array("a ","b ","c ")
val b = Array("x","y")
for (a_ <- a; b_ <- b) yield (a_, b_)

res0: Array[(java.lang.String, java.lang.String)] = Array((a ,x), (a ,y), (b ,x), (b ,y), (c ,x), (c ,y))

Happy coding.

6

I'm using the following extensively in my code. Note that this is working for an arbitrary number of lists. It is creating an Iterator instead of a collection, so you don't have to store the potentially huge result in memory.

Any improvements are very welcome.

/**
  * An iterator, that traverses every combination of objects in a List of Lists.
  * The first Iterable will be incremented fastest. So consider the head as 
  * the "least significant" bit when counting.*/

class CombinationIterator[A](val components: List[Iterable[A]]) extends Iterator[List[A]]{
  private var state: List[BufferedIterator[A]] = components.map(_.iterator.buffered)
  private var depleted = state.exists(_.isEmpty)

  override def next(): List[A] = {
    //this function assumes, that every iterator is non-empty    
    def advance(s: List[(BufferedIterator[A],Iterable[A])]): List[(BufferedIterator[A],A)] = {
      if( s.isEmpty ){
        depleted = true
        Nil
      }
      else {
        assert(!s.head._1.isEmpty)

        //advance and return identity
        val it = s.head._1
        val next = it.next()
        if( it.hasNext){
          //we have simply incremented the head, so copy the rest
          (it,next) :: s.tail.map(t => (t._1,t._1.head))
        } else {
          //we have depleted the head iterator, reset it and increment the rest
          (s.head._2.iterator.buffered,next) :: advance(s.tail)
        }
      }
    }
    //zipping the iterables to the iterators is needed for resseting them
    val (newState, result) = advance(state.zip(components)).unzip
    
    //update state
    state = newState    
    
    result
  }

  override def hasNext = !depleted
}

So using this one, you have to write new CombinationIterator(List(a,b)) to obtain an iterator that goes through every combination.

Edit: based on user unkown's version

Note that the following version is not optimal (performance wise):

  • indexed access into lists (use arrays instead)
  • takeWhile evaluates after every element

.

scala> def combination(xx: List[List[_]], i: Int): List[_] = xx match {
     | case Nil => Nil
     | case x :: xs => x(i % x.length) :: combination(xs, i/x.length)
     | }
combination: (xx: List[List[_]], i: Int)List[_]

scala> def combinationIterator(ll: List[List[_]]): Iterator[List[_]] = {
     | Iterator.from(0).takeWhile(n => n < ll.map(_.length).product).map(combination(ll,_))
     | }
combinationIterator: (ll: List[List[_]])Iterator[List[_]]

scala> List(List(1,2,3),List("a","b"),List(0.1,0.2,0.3))
res0: List[List[Any]] = List(List(1, 2, 3), List(a, b), List(0.1, 0.2, 0.3))
    
scala> combinationIterator(res0)
res1: Iterator[List[_]] = non-empty iterator

scala> res1.mkString("\n")
res2: String = 
List(1, a, 0.1)
List(2, a, 0.1)
List(3, a, 0.1)
List(1, b, 0.1)
List(2, b, 0.1)
List(3, b, 0.1)
List(1, a, 0.2)
List(2, a, 0.2)
List(3, a, 0.2)
List(1, b, 0.2)
List(2, b, 0.2)
List(3, b, 0.2)
List(1, a, 0.3)
List(2, a, 0.3)
List(3, a, 0.3)
List(1, b, 0.3)
List(2, b, 0.3)
List(3, b, 0.3)
Community
  • 1
  • 1
ziggystar
  • 28,410
  • 9
  • 72
  • 124
6

For a list of a unknown number of lists, of different length, and for maybe different types, you can use this:

def xproduct (xx: List [List[_]]) : List [List[_]] = 
  xx match {
    case aa :: bb :: Nil => 
      aa.map (a => bb.map (b => List (a, b))).flatten       
    case aa :: bb :: cc => 
      xproduct (bb :: cc).map (li => aa.map (a => a :: li)).flatten
    case _ => xx
}

You would call it

xproduct List (List ("a ", "b ", "c "), List ("x", "y"))

but can call it with Lists of different kind too:

scala>  xproduct (List (List ("Beatles", "Stones"), List (8, 9, 10), List ('$', '€')))  
res146: List[List[_]] = List(List(Beatles, 8, $), List(Stones, 8, $), List(Beatles, 8, €), List(Stones, 8, €), List(Beatles, 9, $), List(Stones, 9, $), List(Beatles, 9, €), List(Stones, 9, €), List(Beatles, 10, $), List(Stones, 10, $), List(Beatles, 10, €), List(Stones, 10, €))

Arrays have to be converted to Lists, and the result converted back to Arrays, if you can't use Lists.

update:

On the way towards a lazy collection, I made a functional mapping from an index (from 0 to combination-size - 1) to the result at that position, easily calculated with modulo and division, just a bit concentration is needed:

def combicount (xx: List [List[_]]): Int = (1 /: xx) (_ * _.length)

def combination (xx: List [List[_]], i: Int): List[_] = xx match {
    case Nil => Nil
    case x :: xs => x(i % x.length) :: combination (xs, i / x.length)
}

def xproduct (xx: List [List[_]]): List [List[_]] = 
  (0 until combicount (xx)).toList.map (i => combination (xx, i))

It's no problem to use a long instead, or even BigInt.

update 2, The iterator:

class Cartesian (val ll: List[List[_]]) extends Iterator [List[_]] {

  def combicount (): Int = (1 /: ll) (_ * _.length)

  val last = combicount - 1 
  var iter = 0
  
  override def hasNext (): Boolean = iter < last
  override def next (): List[_] = {
    val res = combination (ll, iter)
    iter += 1
    res
  }

  def combination (xx: List [List[_]], i: Int): List[_] = xx match {
      case Nil => Nil
      case x :: xs => x (i % x.length) :: combination (xs, i / x.length) 
  }
}
Community
  • 1
  • 1
user unknown
  • 35,537
  • 11
  • 75
  • 121
  • Can you turn this into a "lazy" collection, e.g. a view? – ziggystar May 11 '11 at 08:14
  • I don't think so. Before producing the first result for N lists, all results of combining N-1 lists are produced, so in the example above, (B8, B9, B10, S8, S9, S10) are combined, before the first iks combined to ('$', '€'). – user unknown May 11 '11 at 15:33
  • .. but you can operate with modulo and division of sublist lengths, to pick certain combinations from a List of List (or Array of Arrays), so for 2*3*2=12 elements, you can enumerate your Collection of Collections, and just return one combination for each call. This should be easily combined with an lazy collection, shouldn't it? I'll try it. – user unknown May 11 '11 at 16:24
  • @ziggystar: I transformed it to a method, which calculates every combination for an index. But I'm not familiar with lazy collections. How do I turn it into a lazy collection? Where do I find a brief tutorial/explanation, to do it myself? – user unknown May 11 '11 at 17:12
  • I didn't check if your code works, but the idea sounds reasonable. I also think it's more readable than my version and can be made more efficient by using Arrays. Also the stored state is only one integer (or better make it a long). But it's limited in sequence length by the size of long. – ziggystar May 11 '11 at 18:12
  • @ziggystar: after locking at an example, dealing with views, I have an idea, but I don't know whether it is good: `(0 until combicount (xx) view).toList.map (i => combination (xx, i))`. Wouldn't the 'toList' be a sabotage to the view? When I call 'xproduct (xx).take (3)' - will it just produce 3 combinations `xproduct (List (List ("Foo", "Bar"), List (8, 9, 10), List ('$', '€'))).take (3).mkString ("\n")`? Okay - I should rewrite my code to find out myself, perhaps. – user unknown May 11 '11 at 18:13
  • How about turning it into an Iterator? – ziggystar May 11 '11 at 18:15
  • @ziggystar: Made an iterator. – user unknown May 11 '11 at 22:23
  • You can use `Iterator.from(0).takeWhile(in.map(_.length).product - 1).map(combination(ll, _))` – ziggystar May 12 '11 at 07:30
  • @Ziggystar: error: not found: value in. You're talking about the 1st update block? In is the List of List, passed as parameter? I'm not sure how to use your code snipplet. – user unknown May 12 '11 at 11:21
  • @uk Sorry, should have been `ll` indstead of `in`. The code should replace your update2 (but still `def combination` is needed). It avoids subclassing `Iterator`. And it should be `takeWhile(_ < ...)`. But I fear that the function in `takeWhile` will be evaluated every step. But the idea is to take an Iterator that goes from 0 to `combicount` and simply map it using `combination`. – ziggystar May 12 '11 at 11:39
  • Then I get a type mismatch for `product-1`. `:8: error: type mismatch; found : Int required: (Int) => Boolean` – user unknown May 12 '11 at 11:47
  • I've added my suggestion to my anser. – ziggystar May 12 '11 at 13:34
  • Ah, I see, and could reproduce it. – user unknown May 12 '11 at 14:05
4

If you want to show off your deep knowledge of higher kinded types and category theory, you can write:

trait Applicative[App[_]] {
  def pure[A](a: A): App[A]
  def fmap[A,B](f: A => B, appA: App[A]): App[B]
  def star[A,B](appF: App[A => B], appA: App[A]): App[B]
}

object ListApplicative extends Applicative[List] {
  override def pure[A](a: A): List[A] = List(a)
  override def fmap[A,B](f: A => B, listA: List[A]): List[B] = listA.map(f)
  override def star[A,B](listF: List[A => B], listA: List[A]):List[B] = 
    for(f <- listF; a <- listA) yield f(a)
}

import ListApplicative._

def pairs[A,B](listA: List[A], listB: List[B]) = 
  star(fmap((a:A) => ((b:B) => (a,b)), listA), listB)

Other than that I would prefer pst's solution...

Landei
  • 54,104
  • 13
  • 100
  • 195
  • 2
    Except I don't have a deep knowledge of higher kinded types :D – Geo May 11 '11 at 07:11
  • Nice answer! If anyone thinks this is too complicated, they should read Learn You A Haskell For Great Good. In Haskell, this stuff is already defined in Control.Applicative. Then you just do `(,) <$> ["a","b","c"] <*> ["x", "y"]` – user1158559 Aug 02 '16 at 10:24
2

Here's one more that does the same thing as @ziggystar's last edit but doesn't use indexed access of lists.

def combinationIterator[A](xs: Iterable[Iterable[A]]): Iterator[List[A]] = {
  xs.foldRight(Iterator(List[A]())) { (heads, tails) =>
    tails.flatMap { tail =>
      heads.map(head => head :: tail)
    }
  }
}

And the sugary version:

def combinationIterator[A](xs: Iterable[Iterable[A]]): Iterator[List[A]] = {
  (xs :\ Iterator(List[A]())) { (heads, tails) =>
    for (tail <- tails; head <- heads) yield head :: tail
  }
}
Ian Tabolt
  • 66
  • 1