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 …

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值