【问题标题】:Lazy Cartesian product of several Seqs in ScalaScala 中几个 Seq 的惰性笛卡尔积
【发布时间】:2011-11-30 06:19:36
【问题描述】:

我实现了一个简单的方法来在几个Seqs 上生成笛卡尔积,如下所示:

object RichSeq {
  implicit def toRichSeq[T](s: Seq[T]) = new RichSeq[T](s)
}

class RichSeq[T](s: Seq[T]) {

  import RichSeq._

  def cartesian(ss: Seq[Seq[T]]): Seq[Seq[T]] = {

    ss.toList match {
      case Nil        => Seq(s)
      case s2 :: Nil  => {
        for (e <- s) yield s2.map(e2 => Seq(e, e2))
      }.flatten
      case s2 :: tail => {
        for (e <- s) yield s2.cartesian(tail).map(seq => e +: seq)
      }.flatten
    }
  }
}

显然,这个非常慢,因为它一次计算整个产品。有人在 Scala 中为这个问题实现了惰性解决方案吗?

UPD

好的,所以我在笛卡尔积上实现了一个非常愚蠢但有效的迭代器版本。在这里为未来的爱好者发帖:

object RichSeq {
  implicit def toRichSeq[T](s: Seq[T]) = new RichSeq(s) 
}

class RichSeq[T](s: Seq[T]) {

  def lazyCartesian(ss: Seq[Seq[T]]): Iterator[Seq[T]] = new Iterator[Seq[T]] {

    private[this] val seqs = s +: ss

    private[this] var indexes = Array.fill(seqs.length)(0)

    private[this] val counts = Vector(seqs.map(_.length - 1): _*)

    private[this] var current = 0

    def next(): Seq[T] = {
      val buffer = ArrayBuffer.empty[T]
      if (current != 0) {
        throw new NoSuchElementException("no more elements to traverse")
      }
      val newIndexes = ArrayBuffer.empty[Int]
      var inside = 0
      for ((index, i) <- indexes.zipWithIndex) {
        buffer.append(seqs(i)(index))
        newIndexes.append(index)
        if ((0 to i).forall(ind => newIndexes(ind) == counts(ind))) {
          inside = inside + 1
        }
      }
      current = inside
      if (current < seqs.length) {
        for (i <- (0 to current).reverse) {
          if ((0 to i).forall(ind => newIndexes(ind) == counts(ind))) {
            newIndexes(i) = 0
          } else if (newIndexes(i) < counts(i)) {
            newIndexes(i) = newIndexes(i) + 1
          }
        }
        current = 0
        indexes = newIndexes.toArray
      }
      buffer.result()
    }

    def hasNext: Boolean = current != seqs.length
  }
}

【问题讨论】:

  • 与其手动实现惰性产品,不如尝试重用 Scala 的惰性集合(流和视图) - 请参阅下面的示例链接。

标签: scala scala-collections


【解决方案1】:

这是我对给定问题的解决方案。请注意,懒惰只是在用于理解的“根集合”上使用 .view 引起的。

scala> def combine[A](xs: Traversable[Traversable[A]]): Seq[Seq[A]] =
     |  xs.foldLeft(Seq(Seq.empty[A])){
     |    (x, y) => for (a <- x.view; b <- y) yield a :+ b }
combine: [A](xs: Traversable[Traversable[A]])Seq[Seq[A]]
scala> combine(Set(Set("a","b","c"), Set("1","2"), Set("S","T"))) foreach (println(_))
List(a, 1, S)
List(a, 1, T)
List(a, 2, S)
List(a, 2, T)
List(b, 1, S)
List(b, 1, T)
List(b, 2, S)
List(b, 2, T)
List(c, 1, S)
List(c, 1, T)
List(c, 2, S)
List(c, 2, T)

为了获得这个,我从https://stackoverflow.com/a/4515071/53974 中定义的函数combine 开始,将函数(a, b) =&gt; (a, b) 传递给它。但是,这并不能直接起作用,因为该代码需要(A, A) =&gt; A 类型的函数。所以我只是稍微修改了一下代码。

【讨论】:

    【解决方案2】:

    【讨论】:

    • 请注意,“在 Scala 中将 Set[Set[String]] 扩展为笛卡尔积”的一些答案也很懒惰,并且比上面的代码要少得多。
    【解决方案3】:

    怎么样:

      def cartesian[A](list: List[Seq[A]]): Iterator[Seq[A]] = {
        if (list.isEmpty) {
          Iterator(Seq())
        } else {
          list.head.iterator.flatMap { i => cartesian(list.tail).map(i +: _) }
        }
      }
    

    简单又懒惰;)

    【讨论】:

      【解决方案4】:
      def cartesian[A](list: List[List[A]]): List[List[A]] = {
        list match {
          case Nil => List(List())
          case h :: t => h.flatMap(i => cartesian(t).map(i :: _))
        }
      }
      

      【讨论】:

        【解决方案5】:

        你可以看这里:https://stackoverflow.com/a/8318364/312172 如何将一个数字转换为所有可能值的索引,而不生成每个元素。

        此技术可用于实现流。

        【讨论】:

        • 您可以使用集合上的现有运算符定义笛卡尔积;如果将集合转换为视图,则构造将是懒惰的。请参阅下面的答案。
        猜你喜欢
        • 2012-04-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-06-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-11-25
        相关资源
        最近更新 更多