Swift中的隊列

隊列(Queue)是一種先入先出(First in First Out,簡稱FIFO)的數(shù)據(jù)結構。想象一下,在排隊買飯的時候,站在隊列最前邊的人買完飯之后出去了,然后排在他后面的人接著上前買飯,直到最后一個人也買完飯走了,這是隊列最形象的一個例子(當然,這里不考慮插隊這種敗人品的bug)。我們來看一下隊列結構示意圖:

隊列的結構示意圖.png

根據(jù)隊列的特點,通常情況下,我們需要實現(xiàn)下面這些屬性和方法:

  • enqueue():將元素添加到隊列的末尾;
  • dequeue():刪除隊列中第一個元素,并且將其返回;
  • peek():返回隊列中第一個元素,但是不會將其從隊列中刪除;
  • clear():清空隊列中的元素;
  • count:返回隊列中元素的個數(shù);
  • isEmpty():判斷隊列是否為空,如果是,則返回true,否則返回false;
  • isFull():判斷隊列是否已滿,如果是,則返回true,否則返回false。
  • capacity:用來查看或者設置隊列容量的屬性。

補充一點,數(shù)組的count屬性和capacity屬性是有區(qū)別的。count屬性用來返回數(shù)組中元素的個數(shù),而capacity屬性是用來返回數(shù)組的存儲空間的。

1、隊列的應用

隊列的應用場景也非常的多,比如說你需要按照接收順序來處理相關的數(shù)據(jù)。舉一個典型的例子,為了解決計算機和打印機之間速度不匹配的問題,通常需要設置一個打印數(shù)據(jù)的緩沖區(qū),主機將要輸出的數(shù)據(jù)依次寫入該緩沖區(qū),而打印機則依次從該緩沖區(qū)中取出數(shù)據(jù),此時緩沖區(qū)的邏輯實現(xiàn)就要用到隊列。

2、隊列的實現(xiàn)

和上一篇文章Stack的實現(xiàn)一樣,在實現(xiàn)隊列時,我們依然要用到數(shù)組和泛型語法。數(shù)組用來存儲數(shù)據(jù)元素,而泛型語法在具體的實現(xiàn)時提供必要的靈活性。接下來,我們要依次實現(xiàn)enqueue()、dequeue()、peek()、isEmpty()、isFull()、clear()方法及count屬性:

// 定義一個隊列結構
public struct Queue<T> {
    
    // 數(shù)組用來存儲數(shù)據(jù)元素
    fileprivate var data = [T]()
    
    // 構造方法,用于構建一個空的隊列
    public init() {}
    
    // 構造方法,用于從序列中創(chuàng)建隊列
    public init<S: Sequence>(_ elements: S) where
        S.Iterator.Element == T {
            data.append(contentsOf: elements)
    }
    
    // 將類型為T的數(shù)據(jù)元素添加到隊列的末尾
    public mutating func enqueue(element: T) {
        data.append(element)
    }
    
    // 移除并返回隊列中第一個元素
    // 如果隊列不為空,則返回隊列中第一個類型為T的元素;否則,返回nil。
    public mutating func dequeue() -> T? {
        return data.removeFirst()
    }
    
    // 返回隊列中的第一個元素,但是這個元素不會從隊列中刪除
    // 如果隊列不為空,則返回隊列中第一個類型為T的元素;否則,返回nil。
    public func peek() -> T? {
        return data.first
    }
    
    
    // 清空隊列中的數(shù)據(jù)元素
    public mutating func clear() {
        data.removeAll()
    }
    
    
    // 返回隊列中數(shù)據(jù)元素的個數(shù)
    public var count: Int {
        return data.count
    }
    
    // 返回或者設置隊列的存儲空間
    public var capacity: Int {
        get {
            return data.capacity
        }
        set {
            data.reserveCapacity(newValue)
        }
    }
    
    // 檢查隊列是否已滿
    // 如果隊列已滿,則返回true;否則,返回false
    public func isFull() -> Bool {
        return count == data.capacity
    }
    
    // 檢查隊列是否為空
    // 如果隊列為空,則返回true;否則,返回false
    public func isEmpty() -> Bool {
        return data.isEmpty
    }
}

上面的代碼定義了一個基本的隊列,下面來演示一下將數(shù)據(jù)元素添加到隊列,以及從隊列中刪除數(shù)據(jù)元素的基本操作:

// 聲明一個存儲String類型數(shù)據(jù)的隊列
var queue = Queue<String>()

// 將風塵四俠添加到隊列中
queue.enqueue(element: "LeBron James")
queue.enqueue(element: "Carmelo Anthony")
queue.enqueue(element: "Dwyane Wade")
queue.enqueue(element: "Chris Paul")

// 從隊列中取出LeBron James,并將其刪除
let x = queue.dequeue()

// 從隊列中取出Carmelo Anthony,但是不要從隊列中刪除
let y = queue.peek()

// 刪除隊列中的第一個元素(此時返回的應該是Carmelo Anthony)
let z = queue.dequeue()

關于上面隊列的代碼,其功能還是相當簡單的,我們只是將一個數(shù)組包裝成了一個隊列,但是距離真正的隊列還有一段距離。另外需要強調一點,此時隊列的存儲容量是由數(shù)組自己動態(tài)調整來實現(xiàn)的,我們并沒有對其進行干預。

3、通過協(xié)議來擴展隊列的功能

要想讓在上面定義的Queue類型看起來更像一個隊列,我們還需要利用一些便利協(xié)議來擴展其功能。首先,我們先給它擴展CustomStringConvertible協(xié)議和CustomDebugStringConvertible協(xié)議,這會讓我們在打印隊列時,控制臺會輸出簡介漂亮的格式:

// 讓打印隊列時輸出簡介的格式
extension Queue: CustomStringConvertible, CustomDebugStringConvertible {
    
    // 控制打印隊列時的文本輸出
    public var description: String {
        return data.description
    }
    
    // 控制打印隊列時的文本輸出,主要用于調試
    public var debugDescription: String {
        return data.debugDescription
    }
}

如果我們沒有實現(xiàn)上面的代碼,在打印隊列時,控制臺會顯示下面的字符串:

Queue<String>(data: ["LeBron James", "Carmelo Anthony", "Dwyane Wade", "Chris Paul"])

但是,當我們實現(xiàn)了上面的擴展,再打印隊列時,控制臺會顯示下面這樣的字符串:

["LeBron James", "Carmelo Anthony", "Dwyane Wade", "Chris Paul"]

從對比中我們可以看到,實現(xiàn)上面的擴展之后,控制臺打印出來的字符串看起來更加的友好。

接著,我們要讓隊列可以使用字面語法(也就是快速聲明語法)來初始化一個實例。為此,必須讓隊列遵守ExpressibleByArrayLiteral協(xié)議。要實現(xiàn)這個功能,還要給Queue增加一個新的構造器,該構造器可以初始化出一個序列,讓序列中包含所有初始化的元素:

/// 構造方法,用于從序列中創(chuàng)建隊列(添加到Queue的類型定義中)
    public init<S: Sequence>(_ elements: S) where
        S.Iterator.Element == T {
            data.append(contentsOf: elements)
    }

// 讓隊列支持通過快速聲明來創(chuàng)建實例
extension Queue: ExpressibleByArrayLiteral {
    
    public init(arrayLiteral elements: T...) {
        self.init(elements)
    }
}

// 使用快速語法聲明一個隊列
var myQueue : Queue = [5, 8, 1, 6, 9, 11]
print(myQueue)

除了按照字面意思快速聲明一個隊列之外,我們可能還希望像集合類型一樣,可以在隊列中使用for...in循環(huán)語句。為此,必須讓我們定義的Queue遵守Sequence協(xié)議,這樣它就可以返回一個懶加載的序列:

// 擴展隊列的for...in循環(huán)功能
extension Queue: Sequence {
    
    // 從序列中返回一個迭代器
    public func generate() -> AnyIterator<T> {
        return AnyIterator(IndexingIterator(_elements: data.lazy))
    }
}

寫完上面的代碼之后,Playground可能會報Type 'Queue<T>' does not conform to protocol 'Sequence'的錯誤,主要原因是我們在上面用到了IndexingIterator。IndexingIterator定義在Collection協(xié)議中,并且是繼承自_IndexableBase協(xié)議。而_IndexableBase協(xié)議在Swift 4中將會被移除,所以我們不用鳥它:

_IndexableBase.png

為了實現(xiàn)for...in循環(huán)的功能,同時解決編譯器報錯,我們需要遵守Collection協(xié)議。除此之外,集合類型中另外一個非常有用的協(xié)議是MutableCollection,它允許你使用下標來檢索和設置隊列中的元素。通過下標的使用,我們可以指定隊列中元素的索引。不過,在使用索引之前,我們還需要對它的合法性進行驗證,為此需要實現(xiàn)一個checkIndex()方法:

// 現(xiàn)在Queue的定義中添加一個方法,用于檢查索引的合法性
fileprivate func checkIndex(index: Int) {
    if index < 0 || index > count {
        fatalError("Index out of range")
    }
}

/** 接下來是對Queue進行擴展 **/

// 根據(jù)索引返回指定的位置
extension Queue: Collection {
    
    // i的值必須比endIndex小
    public func index(after i: Int) -> Int {
        // 返回i后面的索引
        return data.index(after: i)
    }
}

// 實現(xiàn)下標功能
extension Queue: MutableCollection {
    
    // 隊列的起始索引
    public var startIndex: Int {
        return 0
    }
    
    // 隊列末尾索引
    public var endIndex: Int {
        return count - 1
    }
    
    // 獲取或者設置下標
    public subscript(index: Int) -> T {
        get {
            checkIndex(index: index)
            return data[index]
        }
        
        set {
            checkIndex(index: index)
            data[index] = newValue
        }
    }
}

實現(xiàn)完上面的代碼之后,我們不僅可以使用for...in循環(huán)來遍歷隊列,還可以獲取隊列中指定位置的索引:

// 遍歷隊列中的元素
for el in myQueue {
    print(el)
}

// 獲取隊列中指定下標后面的索引
myQueue.index(after: 0)

// 獲取隊列中第一個元素的索引
myQueue.startIndex

// 獲取隊列中最后一個元素的索引
myQueue.endIndex

最后,為了便于閱讀和理解,我們還是按照習慣,將所有的代碼整理到一起:

// 定義一個隊列結構
public struct Queue<T> {
    
    // 數(shù)組用來存儲數(shù)據(jù)元素
    fileprivate var data = [T]()
    
    // 構造方法,用于構建一個空的隊列
    public init() {}
    
    // 構造方法,用于從序列中創(chuàng)建隊列
    public init<S: Sequence>(_ elements: S) where
        S.Iterator.Element == T {
            data.append(contentsOf: elements)
    }
    
    // 將類型為T的數(shù)據(jù)元素添加到隊列的末尾
    public mutating func enqueue(element: T) {
        data.append(element)
    }
    
    // 移除并返回隊列中第一個元素
    // 如果隊列不為空,則返回隊列中第一個類型為T的元素;否則,返回nil。
    public mutating func dequeue() -> T? {
        return data.removeFirst()
    }
    
    // 返回隊列中的第一個元素,但是這個元素不會從隊列中刪除
    // 如果隊列不為空,則返回隊列中第一個類型為T的元素;否則,返回nil。
    public func peek() -> T? {
        return data.first
    }
    
    
    // 清空隊列中的數(shù)據(jù)元素
    public mutating func clear() {
        data.removeAll()
    }
    
    
    // 返回隊列中數(shù)據(jù)元素的個數(shù)
    public var count: Int {
        return data.count
    }
    
    // 返回或者設置隊列的存儲空間
    public var capacity: Int {
        get {
            return data.capacity
        }
        set {
            data.reserveCapacity(newValue)
        }
    }
    
    // 檢查隊列是否已滿
    // 如果隊列已滿,則返回true;否則,返回false
    public func isFull() -> Bool {
        return count == data.capacity
    }
    
    // 檢查隊列是否為空
    // 如果隊列為空,則返回true;否則,返回false
    public func isEmpty() -> Bool {
        return data.isEmpty
    }
    
    // 確保隊列中的索引是合法的
    fileprivate func checkIndex(index: Int) {
        if index < 0 || index > count {
            fatalError("Index out of range")
        }
    }
}


/**************** 隊列的基本操作 ****************/



// 聲明一個存儲String類型數(shù)據(jù)的隊列
var queue = Queue<String>()

// 將風塵四俠添加到隊列中
queue.enqueue(element: "LeBron James")
queue.enqueue(element: "Carmelo Anthony")
queue.enqueue(element: "Dwyane Wade")
queue.enqueue(element: "Chris Paul")

// 打印隊列
print(queue)

// 從隊列中取出LeBron James,并將其刪除
let x = queue.dequeue()

// 從隊列中取出Carmelo Anthony,但是不要從隊列中刪除
let y = queue.peek()

// 刪除隊列中的第一個元素(此時返回的應該是Carmelo Anthony)
let z = queue.dequeue()



/**************** 利用協(xié)議來擴展隊列的功能 ****************/


// 讓打印隊列時輸出簡介的格式
extension Queue: CustomStringConvertible, CustomDebugStringConvertible {
    
    // 控制打印隊列時的文本輸出
    public var description: String {
        return data.description
    }
    
    // 控制打印隊列時的文本輸出,主要用于調試
    public var debugDescription: String {
        return data.debugDescription
    }
}

// 讓隊列支持通過快速聲明來創(chuàng)建實例
extension Queue: ExpressibleByArrayLiteral {
    
    public init(arrayLiteral elements: T...) {
        self.init(elements)
    }
}

// 使用快速語法聲明一個隊列
var myQueue : Queue = [5, 8, 1, 6, 9, 11]
print(myQueue)

// 擴展隊列的for...in循環(huán)功能
extension Queue: Sequence {
    
    // 從序列中返回一個迭代器
    public func generate() -> AnyIterator<T> {
        return AnyIterator(IndexingIterator(_elements: data.lazy))
    }
}

// 根據(jù)索引返回指定的位置
extension Queue: Collection {
    
    // i的值必須比endIndex小
    public func index(after i: Int) -> Int {
        // 返回i后面的索引
        return data.index(after: i)
    }
}

// 實現(xiàn)下標功能
extension Queue: MutableCollection {
    
    // 隊列的起始索引
    public var startIndex: Int {
        return 0
    }
    
    // 隊列末尾索引
    public var endIndex: Int {
        return count - 1
    }
    
    // 獲取或者設置下標
    public subscript(index: Int) -> T {
        get {
            checkIndex(index: index)
            return data[index]
        }
        
        set {
            checkIndex(index: index)
            data[index] = newValue
        }
    }
}

// 編譯器有bug,遍歷顯示有錯,但是仍然可以成功遍歷
for el in myQueue {
    print(el)
}

// 獲取隊列中指定下標后面的索引
myQueue.index(after: 0)

// 獲取隊列中第一個元素的索引
myQueue.startIndex

// 獲取隊列中最后一個元素的索引
myQueue.endIndex

好了,Swift隊列數(shù)據(jù)結構的基本實現(xiàn)就先整理到這里,在下一篇中,我們將會繼續(xù)學習環(huán)形緩沖區(qū)的相關知識。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,443評論 6 532
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,530評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,407評論 0 375
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,981評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,759評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,204評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,263評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,415評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 48,955評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,782評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,983評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,528評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,222評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,650評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,892評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,675評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,967評論 2 374

推薦閱讀更多精彩內容