【问题标题】:Scala implementation of a generic Heap通用堆的 Scala 实现
【发布时间】:2014-09-22 11:40:59
【问题描述】:

我正在尝试实现一个非常基本的泛型堆实现,虽然我喜欢类型检查器,但这是我觉得它每一步都在与我作斗争的场合之一。

我能写的最简单的表达式是:

trait Heap[A] {
  def isEmpty: Boolean
  def merge(as: Heap[A]): Heap[A]
  def insert(a: A): Heap[A]
  def findMin: A
  def deleteMin(): Heap[A]
}

这很好,但只要调用mergeinsertdeleteMin,具体实现就会“松散”它们的类型。也就是说,如果set 的类型为CustomHeap,则调用set.deleteMin 的类型为Heap

经过一番努力,我想出了解决这个问题的以下定义:

trait Heap[A, Repr <: Heap[A, Repr]] {
  def isEmpty: Boolean
  def merge(as: Repr): Repr
  def insert(a: A): Repr
  def findMin: A
  def deleteMin(): Repr
}

这开始变得复杂,但可以按预期工作:它是一个通用的 Heap,例如,在调用 merge 时类型不会丢失。

当一个人试图不将自己的代码绑定到Heap 的特定实现时,这个定义有点麻烦,但是:变量不能是Heap[A] 类型,而是更复杂的东西,我很快就放弃尝试编写.

为了解决这个限制,我尝试使用集合 API 中随处可见的 XxxLike 模式,但这就是我卡住的地方。

这就是我目前所拥有的:

trait Heap[A] extends HeapLike[A, Heap[A]]

trait HeapLike[A, +Repr <: HeapLike[A, Repr] with Heap[A]] {
  def isEmpty: Boolean
  def merge(bs: Repr): Repr
  def insert(a: A): Repr
  def findMin: A
  def deleteMin(): Repr
}

这有点复杂,并且引入了一个新特性,只是为了打字,但我可以忍受 - 如果它有效的话。

在这个实现中,HeapLikeRepr 上是协变的,但Reprmerge 的一个参数——在逆变位置的协变类型。我无法解决这个问题。

我还尝试使HeapLikeRepr 上保持不变,直到我尝试实际混合该特征:

sealed trait LeftistHeap[A] extends Heap[A] with HeapLike[A, LeftistHeap[A]] {
  def rank: Int
}

这会产生以下错误消息:

error: illegal inheritance;
self-type this.LeftistHeap[A] does not conform to this.HeapLike[A,this.LeftistHeap[A]]'s selftype this.HeapLike[A,this.LeftistHeap[A]]
sealed trait LeftistHeap[A] extends Heap[A] with HeapLike[A, LeftistHeap[A]] {

我确信有一种简单的方法可以让这一切正常工作 - 它比集合 API 要基本得多,它设法完成所有这些操作并且在包含的元素上保持协变一个集合,但我觉得我撞到了一堵砖墙。有什么建议、解释、指点...?

【问题讨论】:

  • 当我尝试编译你的前两个特征(在 scala 2.11 中)时,我收到了这个错误:&lt;console&gt;:11: error: covariant type Repr occurs in contravariant position in type Repr of value bs def merge(bs: Repr): Repr
  • 如果你的Heap 是可变的,你所有的变异方法(例如mergeinsertdeleteMin 的返回类型可能是this.type,而你将为每个实现返回this。这应该让您完全避免复杂的类型签名和抽象Repr 类型。
  • @Ashalynd 这很奇怪 - 我刚刚尝试使用 scala 2.11.2 编译我的前两个示例,它们运行良好。第三个确实因协变类型的逆变位置错误而失败,但这在我的帖子中提到了。我误解了你的评论吗?
  • @Dylan my Heap 是不可变的,但我以前从未见过this.type。我会查一下,但是否有特殊原因不能在不可变结构的情况下使用它?
  • 我尝试编译您的第三个示例(没有 LeftistHeap 特征)。

标签: scala generics covariance contravariance variance


【解决方案1】:

实际上,Scala 中的方差非常棒,可以让您更简单地解决问题。

让我们采用你原来的Heap trait:

trait Heap[A] {
  def isEmpty: Boolean
  def merge(as: Heap[A]): Heap[A]
  def insert(a: A): Heap[A]
  def findMin: A
  def deleteMin(): Heap[A]
}

...为了使派生集合在应用转换后保持其“种类”,您可以执行以下操作(我在这里演示概念,所以我使用??? 存根方法。此代码将编译但您将显然必须实现它们以使您的堆在运行时工作):

class MyHeap[A] extends Heap[A] {
  override def isEmpty: Boolean = ???
  override def insert(a: A): MyHeap[A] = ???
  override def deleteMin(): MyHeap[A] = ???
  override def merge(as: Heap[A]): MyHeap[A] = ???
  override def findMin: A = ???
}

...然后你可以这样做:

val myHeap1 = new MyHeap[Int]
val myHeap2 = new MyHeap[Int]

val stillMyHeap1: MyHeap[Int] = myHeap1 insert 1
val stillMyHeap2: MyHeap[Int] = myHeap1 merge myHeap2

请注意insertdeleteMinmerge return types are covariant (MyHeap)。

另请注意,merge 采用相同类型的参数 (Heap),但如果您愿意,也可以将其实现为 take contravariant argument type(即 Heap 的假设基数)。

更新

顺便说一句,要使Heap[A](或MyHeap[A])保留其参数类型A的变化,就像标准的Scala集合一样(例如:AnyValInt的基础,有@987654340 @ 成为 Heap[Int] 的基础)你必须像这样改变你的 Heap 特征:

trait Heap[+A] {
  def isEmpty: Boolean
  def merge[B >: A](xs: Heap[B]): Heap[B]
  def insert[B >: A](x: B): Heap[B]
  def findMin: A
  def deleteMin(): Heap[A]
}

... 和MyHeap 像这样:

class MyHeap[+A] extends Heap[A] {
  override def isEmpty: Boolean = ???
  override def insert[B >: A](x: B): MyHeap[B] = ???
  override def deleteMin(): MyHeap[A] = ???
  override def merge[B >: A](xs: Heap[B]): MyHeap[B] = ???
  override def findMin: A = ???
}

...然后你可以像下面这样玩它:

val myHeap1 = new MyHeap[Int]
val myHeap2 = new MyHeap[Int]

val stillMyHeap1: MyHeap[Int] = myHeap1 insert 1
val stillMyHeap2: MyHeap[Int] = myHeap1 merge myHeap2

val myHeap3: MyHeap[AnyVal] = myHeap1
val myHeap4: MyHeap[AnyVal] = myHeap1 insert 'a'

val heap1: Heap[Int] = myHeap1
val heap2: Heap[AnyVal] = myHeap1

注意MyHeap[Int] 如何符合所有这些:

  • MyHeap[AnyVal] (myHeap3)
  • Heap[Int] (heap1)
  • Heap[AnyVal] (heap2)

还要注意 inserting CharMyHeap[Int] 是如何产生 MyHeap[AnyVal] (myHeap4) 的。由于此操作的结果既不能是 MyHeap[Int] 也不能是 MyHeap[Char] 它回退到两个的共同祖先,即 MyHeap[AnyVal]

【讨论】:

  • 这只是解决方案的一部分,不是吗?只要将MyHeap 的实例作为通用Heap 处理,该属性就会丢失。例如,假设您有一个接受Heap 的函数,向它添加一个值并返回新创建的实例。即使在后台,您知道返回值的类型是MyHeap,编译器却没有,您只剩下一个空的Heap。但是,如果表示的类型是在Heap 中编码的(例如在我损坏的HeapLike trait 中),则不会有任何此类问题。
猜你喜欢
  • 2017-07-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-07-28
  • 2017-09-03
  • 2021-04-10
  • 2011-03-17
相关资源
最近更新 更多