实现一个函数式Queue泛型类
函数式队列是一种具有以下三种操作方式的数据结构:
head 返回队列的第一个元素
tail 返回除第一个元素之外的队列
append 返回尾部添加了指定元素的新队列
如果Queue是一个不变队列,也就是函数式队列。在添加元素的时候不会改变其内容,而是返回包含了这个元素的新队列。
如果Queue是可变类型的,那么append操作将改变队列的内容。
纯函数式队列和List具有相似性,都支持head和tail操作。可以通过List来实现函数式队列。
关于时间开销问题,append操作应该在常量时间内完成。
为了做到这一点,我们使用两个List,分别称为leading和trailing,来表达队列。leading包含前段元素,而trailing包含了反向排列的后段元素。队列在任何时刻的所有内容都可以表示为leading ::: trailing.reverse
。
- 想要添加新元素,只要使用::操作符将其添加到trailing,使得append是常量时间。
- 当原始的空队列通过后继的append操作构建起来时,trailing将不断增加,而leading始终是空白的。于是,在对空的leading第一次执行head或者tail操作之前,trailing应该被反转并复制给leading,这个操作称为mirror。
- mirror操作花费的时间大概与队列的元素数量成正比,但仅在leading为空时。如果leading非空,它将直接返回。
- 因为head和tail调用了mirror,所以他们的复杂度与队列长度呈线性关系。实际上,假设leading为空时,mirror才翻转并复制,那么n个元素需要进行n次tail之后才进行复制。这n次tail操作平均分担了mirror操作的时间复杂度,也相当于常量时间了。
代码如下:
class Queue[T] (private val leading: List[T],
private val trailing: List[T])
{
private def mirror =
if (leading.isEmpty)
new Queue(trailing.reverse, Nil)
else
this
def head = mirror.leading.head
def tail = {
val q = mirror
new Queue(q.leading.tail, q.trailing)
}
def append(element: T) =
new Queue(leading, element :: trailing)
}
信息隐藏
私有构造器和工厂方法
上面实现的Queue以暴露本不该暴露的实现细节为代价,全局可访问Queue构造器,带有两个列表参数,不能作为直观表达队列的形式。
私有构造器和私有成员是隐藏类的初始化代码和表达代码的一种方式。
可以通过把private修饰符添加在类参数列表的前面把主构造器隐藏起来。
class Queue[T] private (private val leading: List[T],
private val trailing: List[T])
构造器是私有的,它只能被类本身及伴生对象访问。我们可以添加可以用初始元素序列创建队列的工厂方法。定义与类同名的Queue对象及apply方法:
object Queue {
def apply[T](xs: T*) = new Queue[T](xs.toList, Nil)
}
私有类
使用私有类的方法可以更彻底的把类本身隐藏掉,仅提供能够暴露类公共接口的特质。
trait Queue[T] {
def head: T
def tail: Queue[T]
def append(x: T): Queue[T]
}
object Queue {
def apply[T](xs: T*): Queue[T] =
new QueueImpl[T](xs.toList, Nil)
private class QueueImpl[T](
private val leading: List[T],
private val trailing: List[T]
) extends Queue[T]
{
def mirror =
if (leading.isEmpty)
new QueueImpl(trailing.reverse, Nil)
else
this
def head: T = mirror.leading.head
def tail: QueueImpl[T] = {
val q = mirror
new QueueImpl(q.leading.tail, q.trailing)
}
def append(x: T) =
new QueueImpl(leading, x:: trailing)
}
}
代码中定义了特质Queue,声明了方法head、tail和append。
这三个方法都实现在子类QueueImpl中,而它本身是对象Queue的内部类。这个方案暴露给客户的信息与前面相同,但使用了不同的技术。代之以逐个隐藏构造器与方法,这个版本隐藏全部实现类。
实现协变的泛型类
使用Queue[+T]方式对Queue实现协变,然而在append的实现中,T参数出现在了逆变的位置。
可以通过把append变为多态以使其泛型化(即提供给append方法类型参数)并使用它的类型参数的下界。
class Queue[+T] private (
private[this] var leading: List[T],
private[this] var trailing: List[T]
) {
def append[U >: T](x: U) =
new Queue[U](leading, x::trailing)
}
通过U >: T
定义了T为U的下界。结果U必须是T的超类型。
假设存在类Fruit和两个子类,Apple和Orange。通过Queue类的定义,可以吧Orange对象加入到Queue[Apple],结果返回Queue[Fruit]类型。
对象私有数据
到目前为止,Queue类仍有一些问题。如果head被一遍遍的调用很多次,而leading列表为空,那么mirror操作可能会重复的把trailing复制到leading列表。
可以将leading和trailing指定为可以重新复制的变量,而mirror从trailing反向复制到leading的操作是在当前队列上的副作用,而不再返回新的队列。
通过将leading和trailing用private[this]修饰,声明为对象私有变量,使得这种副作用纯粹是Queue操作的内部实现,从而使它对于Queue的客户不可见。
代码如下:
class Queue[+T] private (
private[this] var leading: List[T],
private[this] var trailing: List[T]
) {
private def mirror() =
if(leading.isEmpty) {
while(!trailing.isEmpty) {
leading = trailing.head :: leading
trailing = trailing.tail
}
}
def head: T = {
mirror()
leading.head
}
def tail: Queue[T] = {
mirror()
new Queue(leading.tail, trailing)
}
def append[U >: T](x: U) =
new Queue[U](leading, x::trailing)
}
说明:
被定义在同一个对象内访问对象私有变量不会引起与变化型有关的问题。Scala的变化型检查规则包含了关于对象私有定义的特例。当检查到带有+/-号的类型参数只出现在具有相同变化型分类的位置上时,这种定义将被忽略。
转载请注明作者Jason Ding及其出处
jasonding.top
Github博客主页(http://blog.jasonding.top/)
CSDN博客(http://blog.csdn.net/jasonding1354)
简书主页(http://www.jianshu.com/users/2bd9b48f6ea8/latest_articles)
Google搜索jasonding1354进入我的博客主页