Scala method to combine each element of an iterable with each element of another?

后端 未结 5 1398
情深已故
情深已故 2020-12-06 11:53

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

5条回答
  •  醉梦人生
    2020-12-06 12:26

    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)
    

提交回复
热议问题