Swift 面向协议编程实践--数据结构之链表

标题有没有很标题党的样子?
实际上这篇文章改编自我对数据结构链表的笔记,只是我没有想到,当我想要用 Swift 来实现链表的时候,会发生这些有趣的事情。同时还让我对面向协议编程做了一次实践。
于是就有了这么一个唬人的标题,因为实际上我想复习的是链表,只是不小心发现了新大陆。我想这就跟 Bug 差不多,当你解决一个 Bug, 就会产生更多的 Bug.
程序员的生活就是有趣……

C 数据结构 -- 线性表 之 链表

先复习一下链表吧,不然我总感觉不务正业。

  • 定义: 由同类型数据元素构成的有序序列线性结构。
    • 长度: 表中元素的个数
    • 空表: 没有元素的时候
    • 表头: 起始位置
    • 表尾: 结束位置
  • 常用操作
    • 初始化一个空表: List MakeEmpty()
    • 计算长度: int Length(List L)
    • 返回某个元素: ElementType FindK(int K, List L)
    • 查找元素位置: int Find(ElementType X, list L)
    • 插入元素: void Insert(ElementType X, int i, List L)
    • 删除某个元素: void Delete(int i, List L)
  • 实现方式
    • 数组
    • 链表

代码实现

据说一言不合就丢代码是一个程序员的好习惯。总之对这部分没有兴趣的同学可以跳过她,这只是很普通的链表实现,如果你学过数据结构,你就肯定不会陌生。

#include <stdio.h>
#include <stdlib.h>

#define Type int

// MARK: - 线性表 (链表 ChainList)

/// 链表结构
typedef struct ChainListNode {
    Type data;
    struct ChainListNode *next;
} ChainList;

/// 创建空链表初始值为 -1
ChainList *chainListInit() {
    ChainList *list = (ChainList *)malloc(sizeof(ChainList));
    list->data = -1;
    list->next = NULL;
    return list;
}

/// 计算链表长度
int chainListLength(ChainList *list) {
    ChainList *p = list;
    int i = 0;
    while (p) {
        p = p->next;
        i++;
    }
    return i;
}

/// 根据序号查找链表节点,序号从 0 开始
ChainList *chainListFindWithIndex(int index, ChainList *list) {
    ChainList *p = list;
    int i = 0;
    while (p != NULL && i < index) {
        p = p->next;
        i++;
    }
    return p;
}

/// 根据值查找链表节点
ChainList *chainListFindWithData(Type data, ChainList *list) {
    ChainList *p = list;
    while (p != NULL && p->data != data) {
        p = p->next;
    }
    return p;
}

/// 插入: 新建节点; 查找到插入节点的上一个节点; 新节点指向下一个节点; 上一个节点指向新节点。
ChainList *chainListInsert(Type data, int index, ChainList *list) {
    ChainList *p, *n;
    
    // 在头结点处插入
    if (index == 0) {
        n = (ChainList *)malloc(sizeof(ChainList));
        n->data = data;
        n->next = list;
        return n;
    }
    
    // 获取插入位置
    p = chainListFindWithIndex(index, list);
    if (p == NULL) {
        return NULL;
    }
    
    // 插入
    n = (ChainList *)malloc(sizeof(ChainList));
    n->data = data;
    n->next = p->next;
    p->next = n;
    return list;
}

/// 删除节点: 找到前一个节点; 获取删除节点; 前一个节点指向后一个节点; 释放删除节点
ChainList *chainListDelete(int index, ChainList *list) {
    ChainList *p, *d;
    
    // 如果列表为空
    if (list == NULL) {
        return NULL;
    }
    
    // 删除头元素
    if (index == 0) {
        p = list->next;
        free(list);
        return p;
    }
    
    // 查找删除元素的上一个位置
    p = chainListFindWithIndex(index - 1, list);
    if (p == NULL) {
        return NULL;
    }
    
    // 删除
    d = p->next;
    p->next = d->next;
    free(d);
    return list;
}

Swift 面向协议编程

开了那么大篇幅才讲到重点,如果我的职业是编辑的话,估计连睡大街都会被别的小编嫌碍眼。幸运的是,我是个程序员……一步步来是很重要的。

我使用泛型协议来定义了链表。并且使用协议扩展来实现关于链表的常用操作。这样只要任意一个类遵从该协议就可以自动获得这些实现而不需要进行额外的操作。(在我第一次感受到这个特性的强大时,内心被满满的 awesome 刷屏。如果你有我的博客的话,可以在上面看到我利用这个特性实现了 Notify 协议,只要在类声明上添加一下这个协议就可以自动获得一些非常建议的消息发送和接收功能。)

由于语言特性的不同,这个链表跟传统的链表有所不同。你应该通过一个指向头结点的 optional 变量来操作链表,当变量为 nil 时链表为空。

我在代码中除了实现该协议,还写了一个遵从该协议的类作为示例,并有它的调用方法。欢迎吐槽。

// MARK: - 线性表

/* 
在这个泛型协议中,我定义了一个准守 Equatable 协议的泛型 Element, 这是为了后面按值查找的时候可以直接使用等号进行判断。
但实际上这并不是一种聪明的做法,在进行判断的时候完全可以使用闭包来进行处理,这样就能获取更多的类型支持。这里只是为了能表现泛型类型约束的用法,才就这样做。
协议后面的 class 表示这个协议只能被 class 遵从,这种约束是必要的,如果你想使用 struct 类型来实现链表,不是说不可以,但这明显不是一个适用值拷贝场景的地方。
*/
protocol ChainList: class {
    associatedtype Element: Equatable
    var data: Element { get set }
    var next: Self? { get set }
    init()
}

extension ChainList {
    
    /// 返回当前节点到链表结尾的长度
    var length: Int {
        var i = 1
        var p: Self? = self
        while p?.next != nil {
            p = p?.next
            i += 1
        }
        return i
    }
    
    /// 查找元素
    subscript(index: Int) -> Self? {
        var i = 0
        var p: Self? = self
        while p != nil && i < index {
            p = p?.next
            i += 1
        }
        return p
    }
    
    /// 通过值来查找元素
    func find(value: Element) -> Self? {
        var p: Self? = self
        while p != nil && value != p?.data {
            p = p?.next
        }
        return p
    }
    
    /// 插入元素
    @discardableResult func insert(value: Element, to: Int) -> Self? {
        if to == 0 {
            let node  = Self.init()
            node.data = value
            node.next = self
            return node
        }
        
        if let pre = self[to - 1] {
            let node  = Self.init()
            node.data = value
            node.next = pre.next
            pre.next  = node
            return self
        }
        
        return nil
    }
    
    /// 删除元素
    @discardableResult func delete(index: Int) -> Self? {
        if index == 0 {
            return self.next
        }
        
        if let pre = self[index - 1] {
            pre.next = pre.next?.next
            return self
        }
        
        return nil
    }
    
}

// MARK: - 使用示例

/*
遗憾的是,由于协议当中使用了 Self 类型,所以遵从这个协议的类不得不设置为 final。也就是无法继承了。
*/
final class List: ChainList {
    typealias Element = String
    var data: List.Element = ""
    var next: List?
    required init() { }
}

var top: List? = List()
top?.data = "0"

for i in 1 ..< 5 {
    let _ = top?.insert(value: "\(i)", to: i)
}

if let length = top?.length {
    for i in 0 ..< length {
        print(top?[i]?.data)
    }
    
    for _ in 0 ..< length-1 {
        let _ = top?.delete(index: 1)
    }
}

print("Tag")

if let length = top?.length {
    for i in 0 ..< length {
        print(top?[i]?.data)
    }
}

print("Done")

/* 打印输出

Optional("0")
Optional("1")
Optional("2")
Optional("3")
Optional("4")
Tag
Optional("0")
Done
Program ended with exit code: 0

 */

如果你看到这里了,那说明你居然坚持看完了…… awesome ....
我相信你此刻的内心会有一个疑惑,为什么不直接用一个数组呢?Swift 的数组完美的解决了链表所需要解决的问题。是的,之所以这么做,原因只是 because we can ...

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349

推荐阅读更多精彩内容

  • 线性表及其实现 什么是线性表? 谈到线性表,我们先来做个题目!用结构体数组表示一元多项式,并且实现加法操作。 大家...
    lifeColder阅读 1,452评论 0 3
  • 转载自:https://github.com/Tim9Liu9/TimLiu-iOS[https://github...
    香橙柚子阅读 8,496评论 0 35
  • 计划了许久的扬州行,这个清明节终于腾出时间跟盆友去游玩了。心花怒放,欣喜若狂哈哈~~~ 一起出游的那一刻,内心满满...
    瑾瑜菇凉阅读 303评论 0 0
  • 这篇文章我不会过多的讲软件测试的技术知识,因为我始终坚信再好的工具,再好的技术,如果你不能带入思想去应用它们,它们...
    吴小白吃阅读 443评论 0 0
  • 自己太高估自己了~原以为可以接受异地恋自己也可以坚持下来。也以为自己其实可以网恋~并且会有个很好的结局~原本也觉得...
    何处不生烟阅读 185评论 0 0