我是Scala的新手,对于一些相当简单的问题我真的遇到了一些困难。我实现了一个简单的二叉树来存储整数,如下所示:
abstract class IntSet {
def incl(x: Int): IntSet
def contains(x: Int): Boolean
}
class Empty extends IntSet {
def contains(x: Int): Boolean = false
def incl(x: Int): IntSet = new NonEmpty(x, new Empty, new Empty)
}
class NonEmpty(elem: Int, left: IntSet, right: IntSet) extends IntSet {
val head = elem
val leftChild = left
val rightChild = right
def contains(x: Int): Boolean =
if (x < elem) left contains x
else if (x > elem) right contains x
else true
def incl(x: Int): IntSet =
if (x < elem) new NonEmpty(elem, left incl x, right)
else if (x > elem) new NonEmpty(elem, left, right incl x)
else this
}
val my_intset = new NonEmpty(5, new Empty, new Empty)
my_intset.head
val new_intset = my_intset.incl(10)
new_intset.head
遇到两个问题:首先,我无法访问这些类的任何元素,因此实现了字段head
,leftchild
和rightchild
。这使my_intset
可以访问它们。但是,调用incl
后,对象的类型会发生变化,而new_intset
现在变为IntSet
,这使我无法再次访问其中的元素。我如何确保始终可以访问这些字段?
其次,对于incl
的调用,对象的类型会发生变化,我感到很不舒服。从用户的角度来看,我觉得这两个对象都是IntSet
的实例,无论如何这都是我的意图。我有理由担心吗?有没有办法控制这种行为?
答案 0 :(得分:2)
在Scala中,可以利用Abstract Data Types和Pattern Matching来操作此类型。
我们可以稍微修改你的例子。我们将创建一个sealed trait
来定义类型的基本结构:
sealed trait IntSet {
val head: Option[Int]
val left: IntSet
val right: IntSet
def incl(x: Int): IntSet
def contains(x: Int): Boolean
}
现在我们将添加继承的具体案例对象/类:
case class NonEmpty(head: Option[Int], left: IntSet, right: IntSet) extends IntSet {
def contains(x: Int): Boolean = {
if (x < head.getOrElse(0)) left contains x
else if (x > head.getOrElse(0)) right contains x
else true
}
def incl(x: Int): IntSet = {
if (x < head.getOrElse(0)) NonEmpty(head, left incl x, right)
else if (x > head.getOrElse(0)) NonEmpty(head, left, right incl x)
else this
}
}
case object Empty extends IntSet {
override val head: Option[Int] = None
override val left: IntSet = Empty
override val right: IntSet = Empty
def contains(x: Int): Boolean = false
def incl(x: Int): IntSet = NonEmpty(Some(x), Empty, Empty)
}
请注意Empty
是case object
的方式。由于所有空IntSet
都是相同的,我们只需要一个表示一个,我们不需要分配它的实例。另请注意我们如何使用它来表示left
和right
值。
现在,回到原来的例子,这应该可以正常工作:
def main(args: Array[String]): Unit = {
val myIntSet = new NonEmpty(Some(5), Empty, Empty)
println(myIntSet.head)
val newIntSet = myIntSet.incl(10)
println(newIntSet.right.head)
}
此外,当我们想要对IntSet
进行操作时,我们可以对其进行模式匹配以使用底层具体类型:
def isEmpty(intSet: IntSet): Boolean = intSet match {
case Empty => true
case NonEmpty(_, _, _) => false
}
作为旁注,我使用Option[Int]
来代表head
。如果您不介意head
Empty
代替值0,则可以使用Int
。
如果你想要一个类似的例子,我建议looking at the implementation for List[+A]
代数仅定义您操作的类型的另一个示例可能如下所示:
sealed trait BinaryTree
case class Node(value: Int, left: BinaryTree, right: BinaryTree) extends BinaryTree
case object Empty extends BinaryTree
def incl(tree: BinaryTree, x: Int): BinaryTree = {
tree match {
case Node(value, left, right) =>
if (x < value) Node(value, incl(left, x), right)
else if (x > value) Node(value, left, incl(right, x))
else tree
case Empty => Node(x, Empty, Empty)
}
}
现在:
def main (args: Array[String] ): Unit = {
val n = Node(0, Node(1, Empty, Empty), Node(2, Empty, Empty))
val x = incl(n, 3)
println(x)
}
收率:
Node(0,Node(1,Empty,Empty),Node(2,Empty,Node(3,Empty,Empty)))