练习 13:单链表

原文:Exercise 13: Single Linked Lists

译者:飞龙

协议:CC BY-NC-SA 4.0

自豪地采用谷歌翻译

你将实现的第一个数据结构是单链表。我将描述数据结构,列出你应该实现的所有操作,并给你实现需要通过的单个测试。你应该首先尝试使用此数据结构,然后再观看我的实现和审计视频,以便你了解该过程。

警告

这些都不是数据结构的高效实现。它们故意做成朴素和缓慢的,以便我们可以在练习 18 和 19 中讲解度量和优化。如果你在行业工作中尝试使用这些数据结构,就会有性能问题。

描述

在面向对象语言(如 Python)中处理许多数据结构时,你需要理解三个常见概念:

  • “节点”,通常是数据结构的容器或存储单元。你的值保存在这里。
  • “边”,但我们会叫它“指针”或“链接”,它指向其他节点。这些都放在每个节点内,通常作为实例变量。
  • “控制器”,它是一些类,知道如何使用节点中的指针来正确构造数据。

在 Python 中,我们将映射这些概念,如下所示:

  • 节点只是一个类定义的对象。
  • 指针(边)只是节点对象中的实例变量。
  • 控制器是另一个简单的类,它使用节点存储所有内容并构建数据。这是所有的操作(pushpoplist等)的地方,通常控制器的使用者从来没有真正处理节点或指针。

在一些关于算法的书中,你将看到这样的实现,将节点和控制器组合成一个类,但这是非常混乱的,也违反了设计中的问题分离。最好将节点与控制类分开,以便只做一件事并且把它做好,以及你知道错误在哪里。

想象一下,我们想要存储一系列汽车。我们有第一辆车,后面是第二辆,直到最后一辆。想象这个列表,我们可以开始设想一个节点/指针/控制器设计:

  • 节点包含每个车的描述。也许这只是一个Car类的node.value变量。如果你很懒,我们可以调用这个SingleLinkedListNodeSLLNode
  • 然后,每个SLLNode具有一个链接,指向链表中下一个节点。访问node.next可以让你访问下一辆车。
  • 控制器,简单地称为SingleLinkedList,具有诸如pushpopfirstcount之类的操作,它们接受Car,并且使用节点在内部进行存储。当你将汽车pushSingleLinkedList控制器上时,它将处理在一个节点的内部链表,来将其存储在最后。

当 Python 有个相当好用并且快速的list时,为什么我们要这么做呢?完全是为了学习数据结构。在真实世界中,你可以使用 Python 的list并继续。

为了实现SingleLinkedListNode,我们需要一个简单的类,如下:

class SingleLinkedListNode(object):def __init__(self, value, nxt, prev):self.value = valueself.next = nxtdef __repr__(self):nval = self.next and self.next.value or Nonereturn f"[{self.value}:{repr(nval)}]"

我们必须使用单词nxt,因为next是 Python 中的保留字。除此之外,这是一个非常简单的课程。最复杂的是__repr__函数。当你使用%r格式或在节点上调用repr()时,这会打印调试输出。它应该返回一个字符串。

现在花时间了解如何使用SingleLinkedListNode类手动构建列表,然后手动遍历它。这是一个很好的45分钟 hack spike,尝试练习它。

控制器

一旦我们在SingleLinkedListNode类中定义了我们的节点,我们可以确切地知道控制器应该做什么。每个数据结构都有所需的常用操作列表,使其有用。不同的操作花费不同的内存(空间)和时间,一些是昂贵的,另一些是快速的。SingleLinkedListNode的结构使得一些操作非常快,但是许多其他操作非常慢。在实现过程中,你将会了解到它。

查看操作的最简单方法是,查看SingleLinkedList类的框架版本:

class SingleLinkedList(object):def __init__(self):self.begin = Noneself.end = Nonedef push(self, obj):"""将新的值附加到链表尾部。"""def pop(self):"""移除最后一个元素并返回它。"""def shift(self, obj):"""将新的值附加到链表头部。"""def unshift(self):"""移除第一个元素并返回它。"""def remove(self, obj):"""寻找匹配的元素并从中移除。"""def first(self):"""返回第一个元素的*引用*,不要移除。"""def last(self):"""返回最后一个元素的*引用*,不要移除。"""def count(self):"""计算链表中的元素数量。"""def get(self, index):"""获取下标处的值。"""def dump(self, mark):"""转储链表内容的调试函数。"""

在其他练习中,我只会告诉你这些操作,并留给你来弄清楚,但是对于这个练习,我会指导你实现。查看SingleLinkedList中的函数列表,来查看每个操作以及如何使用的注释。

测试

我现在要向你提供测试,实现这个类时,你必须使其能够工作。你会看到我已经遍历了每一个操作,并试图覆盖大部分的边界情况,但是当我进行审计时,你会发现实际上我可能错过了一些。人们常常不会对一些案例进行测试,例如“零个元素”和“一个元素”。

from sllist import *def test_push():colors = SingleLinkedList()colors.push("Pthalo Blue")assert colors.count() == 1colors.push("Ultramarine Blue")assert colors.count() == 2def test_pop():colors = SingleLinkedList()colors.push("Magenta")colors.push("Alizarin")assert colors.pop() == "Alizarin"assert colors.pop() == "Magenta"assert colors.pop() == Nonedef test_unshift():colors = SingleLinkedList()colors.push("Viridian")colors.push("Sap Green")colors.push("Van Dyke")assert colors.unshift() == "Viridian"assert colors.unshift() == "Sap Green"assert colors.unshift() == "Van Dyke"assert colors.unshift() == Nonedef test_shift():colors = SingleLinkedList()colors.shift("Cadmium Orange")assert colors.count() == 1colors.shift("Carbazole Violet")assert colors.count() == 2assert colors.pop() == "Cadmium Orange"assert colors.count() == 1assert colors.pop() == "Carbazole Violet"assert colors.count() == 0def test_remove():colors = SingleLinkedList()colors.push("Cobalt")colors.push("Zinc White")colors.push("Nickle Yellow")colors.push("Perinone")assert colors.remove("Cobalt") == 0colors.dump("before perinone")assert colors.remove("Perinone") == 2colors.dump("after perinone")assert colors.remove("Nickle Yellow") == 1assert colors.remove("Zinc White") == 0def test_first():colors = SingleLinkedList()colors.push("Cadmium Red Light")assert colors.first() == "Cadmium Red Light"colors.push("Hansa Yellow")assert colors.first() == "Cadmium Red Light"colors.shift("Pthalo Green")assert colors.first() == "Pthalo Green"def test_last():colors = SingleLinkedList()colors.push("Cadmium Red Light")assert colors.last() == "Cadmium Red Light"colors.push("Hansa Yellow")assert colors.last() == "Hansa Yellow"colors.shift("Pthalo Green")assert colors.last() == "Hansa Yellow"def test_get():colors = SingleLinkedList()colors.push("Vermillion")assert colors.get(0) == "Vermillion"colors.push("Sap Green")assert colors.get(0) == "Vermillion"assert colors.get(1) == "Sap Green"colors.push("Cadmium Yellow Light")assert colors.get(0) == "Vermillion"assert colors.get(1) == "Sap Green"assert colors.get(2) == "Cadmium Yellow Light"assert colors.pop() == "Cadmium Yellow Light"assert colors.get(0) == "Vermillion"assert colors.get(1) == "Sap Green"assert colors.get(2) == Nonecolors.pop()assert colors.get(0) == "Vermillion"colors.pop()assert colors.get(0) == None

仔细研究此测试,以便你在尝试实现之前,先了解每个操作应如何工作。我不会一次将所有这些代码写入文件。相反,最好每次只做一个测试,并使其小部分能够工作。

这里,如果你不熟悉自动化测试,你可能想要观看视频,来看我怎么做。

审计入门

当你执行每个测试时,你将审计代码来找到缺陷。最终,你将跟踪你在审计中找到的缺陷数量,但现在你需要在写完代码之后执行审计。“审计”类似于政府认为你偷税漏税的时候,税务局所做的工作。他们遍历每笔交易,每笔收入金额,所有支出金额,以及你为什么这样来花费。代码审核与之类似,因为你遍历每个函数,并分析所有输入参数,以及所有输出值。

要进行基本的审计,你将执行此操作:

  • 从你的测试用例开始。在这个例子中我们来审计test_push
  • 查看第一行代码,并确定正在调用什么以及正在创建什么。在这种情况下,它的colors = SingleLinkeList()。这意味着我们正在创建colors变量,并调用SingleLinkeList.__ init__函数。
  • 跳到__init__函数的顶部,保持测试用例和目标函数(__init__)并排。确认你已经这样做了。然后,确认你使用数值和类型正确的函数参数来调用它。在这种情况下__init__只需要self,它应该是正确的类型。
  • 然后进入__init__并逐行审计,以相同的方式确认每个函数调用和变量。它的参数数量正确吗?类型正确吗?
  • 在每个分支(if语句,for循环,while循环)中,确认逻辑是正确的,并且它处理逻辑中的任何可能的条件。if语句的else子句有错误吗?循环能结束吗?然后潜入每个分支,以相同方式跟踪函数,潜入,检查变量,回来,并检查返回值。
  • 当你到达一个函数结尾或任何return的时候,跳回到test_push调用者,来检查返回值是否匹配期望值,当你调用它的时候。记住,尽管如此,你也可以对__init__中的每个调用搞这么做。
  • 最后,当你到达test_push函数的末尾时,你就完成了,并且已经完成了它调用的每个函数的递归检查。

这个流程一开始似乎很乏味,是的,但是你会越来越快,在视频中你会看到,在运行每个测试之前我都这么做(或至少我真的努力尝试这么做)。我按照以下流程:

  • 写一些测试代码。
  • 编写代码使测试工作。
  • 审计二者。
  • 运行测试,看看我是否正确。

挑战练习

我们现在到达了这个部分,你已经准备好尝试它了。首先,浏览测试并研究它的作用,并研究sllist.py中的代码,来弄清楚你需要做什么。我建议当你尝试在SingleLinkeList中实现一个函数时,首先写一些注释来描述它做了什么,然后填充 Python 代码来使这些注释工作。你会看到我在视频中这样做。

当你花了一两个 45 分钟的会话来 Hack 它并试图让它工作时,现在是观看视频的时候了。你首先需要尝试它,以便更好地了解我正在尝试的事情,这样可以使视频更容易理解。视频中我只是编程而不说话,但我会做一个旁白来讨论发生了什么。视频也更快来节省时间,我会剪切掉任何无聊的错误或时间的浪费。

一旦你看到我是怎么做的,你已经做了笔记(对吗?),然后去尝试更严格的东西,并尽可能仔细地执行代码审核过程。

审计

编写代码后,请确保执行第三部分中描述的审计流程。如果你不太确定如何完成,我也将在视频中为这个练习执行审计。

深入学习

为这次练习准备的深入学习是,完全根据我在第三部分的介绍中描述的方式,尝试再次实现该算法。你还应该尝试思考,这个数据结构中的哪些操作最有可能很慢。完成后,对你创建的内容执行审计。

笨办法学 Python · 续 练习 13:单链表相关推荐

  1. 笨办法学 Python · 续 中文版

    笨办法学 Python · 续 中文版 原书:Learn More Python 3 The Hard Way 译者:飞龙 自豪地采用谷歌翻译 在线阅读 PDF格式 EPUB格式 MOBI格式 代码仓 ...

  2. 笨办法学 Python · 续 练习 33:解析器

    练习 33:解析器 原文:Exercise 33: Parsers 译者:飞龙 协议:CC BY-NC-SA 4.0 自豪地采用谷歌翻译 想象一下,你将获得一个巨大的数字列表,你必须将其输入到电子表格 ...

  3. 笨办法学 Python · 续 练习 0:起步

    练习 0:起步 原文:Exercise 0: The Setup 译者:飞龙 协议:CC BY-NC-SA 4.0 自豪地采用谷歌翻译 你需要设置和配置一些工具来学习此书.有可能你已经有了很多这些东西 ...

  4. 笨办法学 Python · 续 第二部分:简单的黑魔法

    第二部分:简单的黑魔法 原文:Part II: Quick Hacks 译者:飞龙 协议:CC BY-NC-SA 4.0 自豪地采用谷歌翻译 你有最好的想法,你会打动世界!你会成为一个亿万富豪!你的大 ...

  5. 笨办法学 Python · 续 练习 52:`moreweb`

    练习 52:moreweb 原文:Exercise 52: moreweb 译者:飞龙 协议:CC BY-NC-SA 4.0 自豪地采用谷歌翻译 现在,你已经使用 Python http.server ...

  6. 笨办法学 Python · 续 练习 24:URL 快速路由

    练习 24:URL 快速路由 原文:Exercise 24: Fast URL Search 译者:飞龙 协议:CC BY-NC-SA 4.0 自豪地采用谷歌翻译 我们将结束数据结构和算法的部分,并将 ...

  7. 笨办法学 Python · 续 练习 15:栈和队列

    练习 15:栈和队列 原文:Exercise 15: Stacks and Queues 译者:飞龙 协议:CC BY-NC-SA 4.0 自豪地采用谷歌翻译 当处理数据结构时,你将经常遇到类似于另一 ...

  8. 笨办法学 Python · 续 练习 32:扫描器

    练习 32:扫描器 原文:Exercise 32: Scanners 译者:飞龙 协议:CC BY-NC-SA 4.0 自豪地采用谷歌翻译 我的第一本书在练习 48 中非常偶然涉及到了扫描器,但现在我 ...

  9. 笨办法学 Python · 续 第三部分:数据结构

    第三部分:数据结构 原文:Part III: Data Structures 译者:飞龙 协议:CC BY-NC-SA 4.0 自豪地采用谷歌翻译 你正在以你的方式构建个人流程,它让你以有限的阻碍快速 ...

最新文章

  1. 【AI】caffe使用步骤(二):设计网络模型prototxt
  2. R语言ggplot2可视化:ggplot2可视化使用labs函数为可视化图像添加(caption)图片说明文字、theme_bw中指定参数base_size来改变图片说明文字、轴标签等的大小
  3. Debug时含有的子元素,在代码里获取不到的问题
  4. Windows 10系统如何使用Ctrl+Alt+Delete解锁屏幕?
  5. python常用的集成开发环境和编辑器有哪些_常用的几款Python集成开发环境或者编辑器详解...
  6. Spring教程--IOC(控制反转)详解
  7. matlab方位探测处理,急大神帮忙,谁有MATLAB解算像片内外方位元素的程序
  8. [AWS vs Azure] 云计算里AWS和Azure的探究(2)
  9. React开发(273):异步调用的方式
  10. Python遍历破解FTP密码,并上传webshell
  11. 卢卡奇总体性原则_重识青年卢卡奇的“历史”概念问题
  12. 2060显卡驱动最新版本_我们平时电脑的驱动需要一直更新吗?教你如何正确的维护你的驱动...
  13. android陀螺仪方向,获得陀螺仪传感器Android的输出
  14. 使用 kind 1 分钟启动一个本地 k8s 开发集群
  15. 增值电信业务经营许可证在怎么申请 办理攻略在这里
  16. 火箭联盟修改车重教程,国服Wegame平台和外服Steam平台通用方法
  17. CF1324F Maximum White Subtree
  18. 神经网络训练梯度算法详解
  19. 快速去除GIF动图的背景(让背景变透明),保姆级教程
  20. 【BLDC理论篇】直流无刷电机控制方法

热门文章

  1. (31)FPGA原语设计(IBUFG)
  2. (9)Zynq UART控制器介绍
  3. 多目标函数 matlab 粒子群_【LIBSVM】基于群智能优化算法的支持向量机 (SVM) 参数优化...
  4. linux下的进程间通信-管道及共享内存
  5. Java的echo_简单的Java echo服务器问题
  6. Linux常用命令小结(一)
  7. springboot+springcloud相关面试题
  8. Linux服务器SSH免密登录
  9. BZOJ 1232 USACO 2008 Nov. 安慰奶牛Cheer
  10. 5-格子染色(递推)