【问题标题】:Scala method to combine each element of an iterable with each element of another?Scala方法将可迭代的每个元素与另一个元素的每个元素结合起来?
【发布时间】:2015-02-14 01:48:13
【问题描述】:

如果我有这个:

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

我想知道是否存在这样的方法,它可以让我遍历第一个集合,并且对于它的每个元素,遍历整个第二个集合。例如,如果我们采用数组a,我们将有a,x,a,y,b,x,b,y,c,x,c,y。我知道 zip,但据我所知,它仅适用于相同大小的集合,并且它关联来自相同位置的元素。

【问题讨论】:

  • 顺便说一句,这被称为两个数组的Cartesian product

标签: scala scala-collections cartesian-product


【解决方案1】:

我不确定“方法”,但这可以用嵌套/复合 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))

编码愉快。

【讨论】:

  • 谢谢!我的版本有点长:)
  • 而“方法”版本是a.flatMap(_a =&gt; b.map(_b =&gt; _a -&gt; _b))
【解决方案2】:

对于未知数量的列表,不同长度的列表,以及可能不同的类型,你可以使用这个:

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
}

你会这样称呼它

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

但也可以使用不同类型的列表来调用它:

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, €))

如果不能使用列表,则必须将数组转换为列表,并将结果转换回数组。

更新:

在走向惰性集合的过程中,我做了一个从索引(从 0 到组合大小 - 1)到该位置结果的函数映射,很容易用模和除法计算,只需要一点注意力:

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))

用 long 代替,甚至 BigInt 都没问题。

更新2,迭代器:

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) 
  }
}

【讨论】:

  • 你能把它变成一个“懒惰”的集合,例如一个视图?
  • 我不这么认为。在产生 N 个列表的第一个结果之前,会产生组合 N-1 个列表的所有结果,因此在上面的示例中,在第一个 iks 组合到 (' $', '€')。
  • .. 但是您可以使用子列表长度的模和除法来从列表列表(或数组数组)中选择某些组合,因此对于 2*3*2=12 个元素,您可以枚举您的 Collection of Collections,并为每次调用返回一个组合。这应该很容易与惰性集合结合起来,不是吗?我试试看。
  • @ziggystar:我将其转换为一种方法,该方法计算索引的每个组合。但我不熟悉惰性集合。我如何把它变成一个惰性集合?我在哪里可以找到简短的教程/说明,自己做?
  • 我没有检查您的代码是否有效,但这个想法听起来很合理。我还认为它比我的版本更具可读性,并且可以通过使用数组来提高效率。此外,存储的状态只有一个整数(或者最好将其设为长整数)。但它的序列长度受long的大小限制。
【解决方案3】:

我在我的代码中广泛使用以下内容。请注意,这适用于任意数量的列表。它正在创建一个迭代器而不是一个集合,因此您不必将潜在的巨大结果存储在内存中。

非常欢迎任何改进。

/**
  * 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
}

所以使用这个,你必须写new CombinationIterator(List(a,b)) 来获得一个遍历每个组合的迭代器。

编辑:基于用户未知的版本

请注意,以下版本不是最佳版本(性能方面):

  • 对列表的索引访问(改用数组)
  • takeWhile 在每个元素之后进行评估

.

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)

【讨论】:

    【解决方案4】:

    如果你想炫耀你对高等类型和范畴论的深入了解,你可以这样写:

    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)
    

    除此之外,我更喜欢 pst 的解决方案...

    【讨论】:

    • 除非我对高级类型没有深入的了解:D
    • 不错的答案!如果有人认为这太复杂了,他们应该阅读 Learn You A Haskell For Great Good。在 Haskell 中,这些东西已经在 Control.Applicative 中定义了。然后你就做(,) &lt;$&gt; ["a","b","c"] &lt;*&gt; ["x", "y"]
    【解决方案5】:

    这里还有一个与 @ziggystar 的最后一次编辑做同样的事情,但不使用列表的索引访问。

    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)
        }
      }
    }
    

    还有含糖的版本:

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

    【讨论】:

    • 非常简洁优雅。向你致敬。
    猜你喜欢
    • 1970-01-01
    • 2019-10-09
    • 2017-07-28
    • 2023-03-19
    • 1970-01-01
    • 2015-08-18
    • 1970-01-01
    • 2022-11-15
    • 1970-01-01
    相关资源
    最近更新 更多