【Scala類型系統(tǒng)】函數(shù)式Queue的簡易實現(xiàn)

實現(xiàn)一個函數(shù)式Queue泛型類

函數(shù)式隊列是一種具有以下三種操作方式的數(shù)據(jù)結構:

head 返回隊列的第一個元素
tail 返回除第一個元素之外的隊列
append 返回尾部添加了指定元素的新隊列

如果Queue是一個不變隊列,也就是函數(shù)式隊列座硕。在添加元素的時候不會改變其內(nèi)容咐柜,而是返回包含了這個元素的新隊列。
如果Queue是可變類型的,那么append操作將改變隊列的內(nèi)容讹堤。
純函數(shù)式隊列和List具有相似性祭刚,都支持head和tail操作〈园梗可以通過List來實現(xiàn)函數(shù)式隊列赡磅。

關于時間開銷問題,append操作應該在常量時間內(nèi)完成宝与。
為了做到這一點焚廊,我們使用兩個List,分別稱為leading和trailing习劫,來表達隊列咆瘟。leading包含前段元素,而trailing包含了反向排列的后段元素诽里。隊列在任何時刻的所有內(nèi)容都可以表示為leading ::: trailing.reverse搞疗。

  • 想要添加新元素,只要使用::操作符將其添加到trailing,使得append是常量時間匿乃。
  • 當原始的空隊列通過后繼的append操作構建起來時桩皿,trailing將不斷增加,而leading始終是空白的幢炸。于是泄隔,在對空的leading第一次執(zhí)行head或者tail操作之前,trailing應該被反轉(zhuǎn)并復制給leading宛徊,這個操作稱為mirror佛嬉。
  • mirror操作花費的時間大概與隊列的元素數(shù)量成正比,但僅在leading為空時闸天。如果leading非空暖呕,它將直接返回。
  • 因為head和tail調(diào)用了mirror苞氮,所以他們的復雜度與隊列長度呈線性關系湾揽。實際上,假設leading為空時笼吟,mirror才翻轉(zhuǎn)并復制库物,那么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)
}

信息隱藏

私有構造器和工廠方法

上面實現(xiàn)的Queue以暴露本不該暴露的實現(xiàn)細節(jié)為代價,全局可訪問Queue構造器撵枢,帶有兩個列表參數(shù)特纤,不能作為直觀表達隊列的形式落萎。
私有構造器和私有成員是隱藏類的初始化代碼和表達代碼的一種方式豌鸡。
可以通過把private修飾符添加在類參數(shù)列表的前面把主構造器隱藏起來屹培。

class Queue[T] private (private val leading: List[T],
                        private val trailing: List[T])

構造器是私有的,它只能被類本身及伴生對象訪問沟绪。我們可以添加可以用初始元素序列創(chuàng)建隊列的工廠方法刮便。定義與類同名的Queue對象及apply方法:

object Queue {
  def apply[T](xs: T*) = new Queue[T](xs.toList, Nil)
}

私有類

使用私有類的方法可以更徹底的把類本身隱藏掉空猜,僅提供能夠暴露類公共接口的特質(zhì)绽慈。

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)
  }
}

代碼中定義了特質(zhì)Queue,聲明了方法head辈毯、tail和append坝疼。
這三個方法都實現(xiàn)在子類QueueImpl中,而它本身是對象Queue的內(nèi)部類谆沃。這個方案暴露給客戶的信息與前面相同钝凶,但使用了不同的技術。代之以逐個隱藏構造器與方法唁影,這個版本隱藏全部實現(xiàn)類耕陷。

實現(xiàn)協(xié)變的泛型類

使用Queue[+T]方式對Queue實現(xiàn)協(xié)變掂名,然而在append的實現(xiàn)中,T參數(shù)出現(xiàn)在了逆變的位置哟沫。
可以通過把append變?yōu)槎鄳B(tài)以使其泛型化(即提供給append方法類型參數(shù))并使用它的類型參數(shù)的下界饺蔑。

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]類型。

對象私有數(shù)據(jù)

到目前為止拂蝎,Queue類仍有一些問題穴墅。如果head被一遍遍的調(diào)用很多次,而leading列表為空匣屡,那么mirror操作可能會重復的把trailing復制到leading列表封救。
可以將leading和trailing指定為可以重新復制的變量,而mirror從trailing反向復制到leading的操作是在當前隊列上的副作用捣作,而不再返回新的隊列誉结。
通過將leading和trailing用private[this]修飾,聲明為對象私有變量券躁,使得這種副作用純粹是Queue操作的內(nèi)部實現(xiàn)惩坑,從而使它對于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)
}

說明:
被定義在同一個對象內(nèi)訪問對象私有變量不會引起與變化型有關的問題也拜。Scala的變化型檢查規(guī)則包含了關于對象私有定義的特例以舒。當檢查到帶有+/-號的類型參數(shù)只出現(xiàn)在具有相同變化型分類的位置上時,這種定義將被忽略慢哈。

轉(zhuǎn)載請注明作者Jason Ding及其出處
jasonding.top
Github博客主頁(http://blog.jasonding.top/)
CSDN博客(http://blog.csdn.net/jasonding1354)
簡書主頁(http://www.reibang.com/users/2bd9b48f6ea8/latest_articles)
Google搜索jasonding1354進入我的博客主頁

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蔓钟,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子卵贱,更是在濱河造成了極大的恐慌滥沫,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件键俱,死亡現(xiàn)場離奇詭異兰绣,居然都是意外死亡,警方通過查閱死者的電腦和手機编振,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進店門缀辩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事臀玄∑耙酰” “怎么了?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵健无,是天一觀的道長炫掐。 經(jīng)常有香客問我,道長睬涧,這世上最難降的妖魔是什么募胃? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮畦浓,結果婚禮上痹束,老公的妹妹穿的比我還像新娘。我一直安慰自己讶请,他們只是感情好祷嘶,可當我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著夺溢,像睡著了一般论巍。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上风响,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天嘉汰,我揣著相機與錄音,去河邊找鬼状勤。 笑死鞋怀,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的持搜。 我是一名探鬼主播密似,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼葫盼!你這毒婦竟也來了残腌?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤贫导,失蹤者是張志新(化名)和其女友劉穎抛猫,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體脱盲,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡邑滨,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年日缨,在試婚紗的時候發(fā)現(xiàn)自己被綠了钱反。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖面哥,靈堂內(nèi)的尸體忽然破棺而出哎壳,到底是詐尸還是另有隱情,我是刑警寧澤尚卫,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布归榕,位于F島的核電站,受9級特大地震影響吱涉,放射性物質(zhì)發(fā)生泄漏刹泄。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一怎爵、第九天 我趴在偏房一處隱蔽的房頂上張望特石。 院中可真熱鬧,春花似錦鳖链、人聲如沸姆蘸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽逞敷。三九已至,卻和暖如春灌侣,著一層夾襖步出監(jiān)牢的瞬間推捐,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工侧啼, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留玖姑,地道東北人。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓慨菱,卻偏偏與公主長得像焰络,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子符喝,可洞房花燭夜當晚...
    茶點故事閱讀 42,901評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 背景 一年多以前我在知乎上答了有關LeetCode的問題, 分享了一些自己做題目的經(jīng)驗闪彼。 張土汪:刷leetcod...
    土汪閱讀 12,724評論 0 33
  • 盡管 Grand Central Dispatch (GCD)已經(jīng)存在一段時間了,但并非每個人都知道怎么使用它协饲。這...
    coderFamer閱讀 7,364評論 1 16
  • 第三章 Java內(nèi)存模型 3.1 Java內(nèi)存模型的基礎 通信在共享內(nèi)存的模型里畏腕,通過寫-讀內(nèi)存中的公共狀態(tài)進行隱...
    澤毛閱讀 4,341評論 2 22
  • __block和__weak修飾符的區(qū)別其實是挺明顯的:1.__block不管是ARC還是MRC模式下都可以使用,...
    LZM輪回閱讀 3,284評論 0 6
  • 迎著初春的晨陽 我疾步在平坦的馬路上 微風輕撫我的臉龐 我夾起肩膀緊裹衣裳 初春的溫度還有些冰涼 春節(jié)的幸福時光又...
    石榴園閱讀 205評論 3 2