【问题标题】:abstracting over a collection对集合进行抽象
【发布时间】:2011-09-02 16:04:34
【问题描述】:

最近,我为 Anys 的笛卡尔积编写了一个迭代器,并从 List 的 List 开始,但认识到,我可以轻松切换到更抽象的 trait Seq。

我知道,你喜欢看代码。 :)

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

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

  val last = combicount
  var iter = 0

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

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

还有那个班级的客户:

object Main extends Application {
  val illi = new Cartesian (List ("abc".toList, "xy".toList, "AB".toList))
  // val ivvi = new Cartesian (Vector (Vector (1, 2, 3), Vector (10, 20)))
  val issi = new Cartesian (Seq (Seq (1, 2, 3), Seq (10, 20)))
  // val iaai = new Cartesian (Array (Array (1, 2, 3), Array (10, 20)))

  (0 to 5).foreach (dummy => println (illi.next ()))
  // (0 to 5).foreach (dummy => println (issi.next ()))
}
/*
List(a, x, A)
List(b, x, A)
List(c, x, A)
List(a, y, A)
List(b, y, A)
List(c, y, A)
*/

该代码适用于 Seq 和 Lists(它们是 Seqs),但当然不适用于 Arrays 或 Vector,它们不是 Seq 类型,并且没有 cons-method '::'。

但逻辑也可以用于此类集合。

我可以尝试为向量、数组等编写与 Seq 的隐式转换,或者尝试编写自己的类似实现,或者编写一个 Wrapper,它将集合转换为 Seq 的 Seq,并调用'hasNext' 和 'next' 用于内部集合,并将结果转换为数组、向量或其他。 (我试图实现这样的变通方法,但我必须认识到:这并不容易。对于现实世界的问题,我可能会独立重写迭代器。)

但是,如果我必须处理列表数组或数组列表以及其他混合情况,整个事情就会有点失控。

以最广泛、可能的方式编写算法的最优雅的方式是什么?

【问题讨论】:

  • 您可能需要重新考虑您的方法,并按照 Rex Kerr 在 that question 中的出色建议从头开始。
  • 感谢您的链接。我希望我有时间深入阅读它,并在周末或者今天尝试一下。看起来很有希望。

标签: scala collections abstraction


【解决方案1】:

有两种解决方案。第一个是不要求容器是某个通用超类的子类,而是可以转换为一个(通过使用隐式函数参数)。如果容器已经是所需类型的子类,则有一个预定义的标识转换,它只返回它。

import collection.mutable.Builder
import collection.TraversableLike
import collection.generic.CanBuildFrom
import collection.mutable.SeqLike

class Cartesian[T, ST[T], TT[S]](val ll: TT[ST[T]])(implicit cbf: CanBuildFrom[Nothing, T, ST[T]], seqLike: ST[T] =>  SeqLike[T, ST[T]], traversableLike: TT[ST[T]] => TraversableLike[ST[T], TT[ST[T]]] ) extends Iterator[ST[T]] {

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

  val last = combicount - 1 
  var iter = 0

  override def hasNext (): Boolean = iter < last
  override def next (): ST[T] = {
    val res = combination (ll, iter, cbf())
    iter += 1
    res
  }

  def combination (xx: TT[ST[T]], i: Int, builder: Builder[T, ST[T]]): ST[T] = 
    if (xx.isEmpty) builder.result
    else  combination (xx.tail, i / xx.head.length, builder += xx.head (i % xx.head.length) ) 
}

这类作品:

scala> new Cartesian[String, Vector, Vector](Vector(Vector("a"), Vector("xy"), Vector("AB")))
res0: Cartesian[String,Vector,Vector] = empty iterator

scala> new Cartesian[String, Array, Array](Array(Array("a"), Array("xy"), Array("AB")))
res1: Cartesian[String,Array,Array] = empty iterator

由于 bug https://issues.scala-lang.org/browse/SI-3343,我需要显式传递类型

需要注意的一点是,这比使用存在类型更好,因为在迭代器上调用 next 返回正确的类型,而不是 Seq[Any]。

这里有几个缺点:

  • 如果容器不是所需类型的子类,则将其转换为一个,这会降低性能
  • 该算法不是完全通用的。我们需要将类型转换为 SeqLike 或 TraversableLike 才能使用这些类型提供的功能子集。所以制作转换函数可能很棘手。
  • 如果某些功能在不同的上下文中可以有不同的解释怎么办?例如,一个矩形有两个“长度”属性(宽度和高度)

现在是替代解决方案。我们注意到,我们实际上并不关心集合的类型,只关心它们的能力:

  • TT 应该有foldLeftget(i: Int)(获取头/尾)
  • ST 应该有 lengthget(i: Int) 和一个 Builder

所以我们可以对这些进行编码:

trait HasGet[T, CC[_]]  {
  def get(cc: CC[T], i: Int): T
}

object HasGet {
  implicit def seqLikeHasGet[T, CC[X] <: SeqLike[X, _]] = new HasGet[T, CC] {
    def get(cc: CC[T], i: Int): T = cc(i)
  }

  implicit def arrayHasGet[T] = new HasGet[T, Array] {
    def get(cc: Array[T], i: Int): T = cc(i)
  }
}

trait HasLength[CC] {
  def length(cc: CC): Int 
}

object HasLength {
  implicit def seqLikeHasLength[CC <: SeqLike[_, _]] = new HasLength[CC] {
    def length(cc: CC) = cc.length
  }

  implicit def arrayHasLength[T] = new HasLength[Array[T]] {
    def length(cc: Array[T]) = cc.length
  }

}   

trait HasFold[T, CC[_]] {
  def foldLeft[A](cc: CC[T], zero: A)(op: (A, T) => A): A
}

object HasFold {
  implicit def seqLikeHasFold[T, CC[X] <: SeqLike[X, _]] = new HasFold[T, CC] {
    def foldLeft[A](cc: CC[T], zero: A)(op: (A, T) => A): A = cc.foldLeft(zero)(op)
  }
  implicit def arrayHasFold[T] = new HasFold[T, Array] {
    def foldLeft[A](cc: Array[T], zero: A)(op: (A, T) => A): A =  {
      var i = 0
      var result = zero
      while (i < cc.length) {
        result = op(result, cc(i))
        i += 1
      }
      result
    }
  }
}   

(严格来说,HasFold 不是必需的,因为它的实现是在长度和获取方面,但我在这里添加了它,这样算法会更清晰地翻译)

现在算法是:

class Cartesian[T, ST[_], TT[Y]](val ll: TT[ST[T]])(implicit cbf: CanBuildFrom[Nothing, T, ST[T]], stHasLength: HasLength[ST[T]], stHasGet: HasGet[T, ST], ttHasFold: HasFold[ST[T], TT], ttHasGet: HasGet[ST[T], TT], ttHasLength: HasLength[TT[ST[T]]]) extends Iterator[ST[T]] {

  def combicount (): Int = ttHasFold.foldLeft(ll, 1)((a,l) => a * stHasLength.length(l))

  val last = combicount - 1 
  var iter = 0

  override def hasNext (): Boolean = iter < last
  override def next (): ST[T] = {
    val res = combination (ll, 0, iter, cbf())
    iter += 1
    res
  }

  def combination (xx: TT[ST[T]], j: Int,  i: Int, builder: Builder[T, ST[T]]): ST[T] = 
    if (ttHasLength.length(xx) == j) builder.result
    else  {
      val head = ttHasGet.get(xx, j)
      val headLength = stHasLength.length(head)
      combination (xx, j + 1, i / headLength, builder += stHasGet.get(head, (i % headLength) )) 
    }
}

并使用:

scala> new Cartesian[String, Vector, List](List(Vector("a"), Vector("xy"), Vector("AB")))
res6: Cartesian[String,Vector,List] = empty iterator

scala> new Cartesian[String, Array, Array](Array(Array("a"), Array("xy"), Array("AB")))
res7: Cartesian[String,Array,Array] = empty iterator

Scalaz 可能已经为您预定义了所有这些,不幸的是,我不太了解。

(我需要再次传递类型,因为推断不能推断出正确的类型)

好处是该算法现在是完全通用的,不需要从 Array 到 WrappedArray 的隐式转换就可以工作

练习:为元组定义 ;-)

【讨论】:

  • 我很抱歉这么晚才回复这么重要的帖子,但是当你回答时,我仍然坐在 scala-2.8 上,无法迁移到 2.9。现在我迁移了,我没有忘记测试你的代码,并试图理解它。不幸的是,我不得不提到两三个问题。问题 1 是,第一个代码无法编译(不再编译?)。 (我允许自己在您的代码中插入必要的导入。)我使用的是 2.9.0.1,并得到错误...:
  • 第二个代码编译得很好,但测试它的代码没有:GenericCartesian.scala:118: could not find implicit value for parameter stHasLength: HasLength[Vector[String]] new Cartesian [String, Vector, List] (List (Vector ("a"), Vector ("xy"), Vector ("AB")))('new'下面的错误标记)。当我试图找出如何告诉“笛卡尔”时,我提到过,您提交了一个由 3 个向量组成的向量,每个向量包含 1 个字符串。我的示例使用了不同长度的字符列表;我不确定你的代码应该产生什么,我猜是一个元素(“a”、“xy”、“AB”)。
猜你喜欢
  • 2017-01-09
  • 1970-01-01
  • 1970-01-01
  • 2010-11-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多