Python-应用计算思维(全)

Python 应用计算思维(全)

原文:zh.annas-archive.org/md5/D0EC374B077B4C90AC07697977B27176

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

《Python 应用计算思维》提供了一种实践方法,介绍了实施和相关方法,让您可以立即投入并提高工作效率。使用 Python 的开发人员将能够使用这本实用指南中的计算思维方法解决问题。

本书将帮助您在解决各种领域的现实问题时,培养逻辑处理和算法思维。这是一项必不可少的技能,您应该在这个信息技术的现代时代保持领先地位。开发人员可以将他们对计算思维的知识应用于实践,解决多个领域的问题,包括经济学、数学和人工智能。

本书首先帮助您了解分解、模式识别、模式概括和抽象以及算法设计,并教您如何在设计解决复杂问题的过程中实际应用这些要素。

我们将了解如何使用分解通过可视化表示解决问题。我们还将使用模式概括和抽象来设计解决方案,并建立评估算法解决方案所需的分析技能。我们还将使用 Python 进行统计分析时使用计算思维。

我们将了解设计算法解决方案的输入和输出要求,并使用计算思维来解决数据处理问题。我们将识别逻辑处理中的错误,以完善您的解决方案设计,并在密码学、经济学和机器学习等各个领域应用计算思维。

然后,您将了解问题分析、逻辑推理、算法设计、分类和聚类、数据分析和建模等涉及的各种技术,并了解如何将计算思维要素与这些方面结合起来设计解决方案。

我们还将培养逻辑推理和解决问题的能力,帮助您解决复杂问题。我们还将使用实际示例探索核心计算机科学概念和重要的计算思维要素,并了解如何确定最适合您问题的算法解决方案。

最后,您将了解如何在解决方案设计过程中识别陷阱,以及如何选择正确的功能来创建最佳的算法解决方案。

在阅读完本书之后,您将获得应用计算思维技术于软件开发的信心。

本书适合对象

这本书适用于希望培养解决问题技能和策略的学生、开发人员和专业人士,这些技能涉及编写或调试软件程序和应用程序。需要熟悉 Python 编程。

本书涵盖内容

第一章《计算机科学基础》帮助您了解计算机科学的基本要素,包括理论、设计、计算过程和系统以及计算机。本章重点将放在计算机科学的软件要素上。

第二章《计算思维要素》解释了计算思维的每个要素——分解、模式识别、模式概括和抽象以及算法设计——以及计算思维过程并非线性。相反,开发人员可以在算法设计过程的各个阶段回顾这些要素,直到找到特定问题的解决方案。本章将包括一些简短的相关问题,用于演示如何使用计算思维要素来得出算法。

第三章《理解算法和算法思维》为您介绍了算法及其定义。您还将回顾一些算法,以帮助您在评估算法时培养必要的分析技能。

第四章《理解逻辑推理》探讨了条件语句、算法推理和布尔逻辑等逻辑推理过程。在整个章节中,您将通过真实和相关的问题分析学习基本和中级逻辑处理技能。

第五章《探索问题分析》探讨了问题分析的主题,重点放在问题定义、分解和分析上。为了练习和进一步理解计算思维的第一个元素——分解,您将面临真实和相关的问题。然后,您将能够定义和分解问题,例如识别输入和输出,以及开始规划解决问题所需的其他相关信息。

第六章《设计解决方案和解决过程》为您提供了一个机会,利用先前学习的计算思维过程设计多个问题的解决方案,并开始将逻辑处理纳入其中,以创建解决方案的决策过程的可视化表示。可视化表示包括图表、流程图和其他有用的过程。

第七章《识别解决方案中的挑战》提供了练习识别一些常见错误和/或更好可能解决方案的机会。虽然大多数问题可以用多种算法解决,以满足问题的需求,但有些解决方案最适合特定问题。本章的目标是介绍您在解决方案设计过程中识别一些陷阱。

第八章《Python 简介》介绍了基本的 Python 命令和功能,同时将它们应用于问题。利用计算思维的元素,您将能够通过纳入先前学到的概念来设计解决方案。

第九章《理解输入和输出以设计解决方案算法》帮助您评估问题,以确定设计和实施问题的解决方案算法所需的输入和输出。

第十章《控制流》帮助您更多地了解条件语句以及在使用计算思维和 Python 编程语言解决问题时如何使用forwhile循环。您将应用先前学到的逻辑处理来创建 Python 算法来解决问题。

第十一章《在简单挑战中使用计算思维和 Python》帮助您将先前获得的知识应用于多个学科中解决挑战的计算思维过程,使用真实和相关的例子,以设计每种情景的最佳算法。

第十二章《在实验和数据分析问题中使用 Python》解释了如何利用 Python 的功能来解决涉及实验数据和数据处理的问题。计算思维元素将被用来使用高级功能解决真实和相关的问题。

第十三章, 使用分类和聚类,涵盖了分类和聚类。您将使用 Pandas、Scikit-Learn 和其他软件包来创建训练和测试模型,并学习一些用于大数据分析的基本定义,如机器学习和神经网络模型。

第十四章, 在统计分析中使用计算思维和 Python,深入探讨了与统计分析相关的进一步主题,如导入数据、索引和预处理数据。然后,您将使用数据可视化来决定要探索哪些变量以进行进一步分析。

第十五章, 应用计算思维问题,帮助您解决结合了前几章各个主题的多个问题,以解决问题并设计 Python 算法。您将使用计算思维元素来确定设计模型和解决语言学、密码学等问题所需的功能。

第十六章, 高级应用计算思维问题,通过各种领域的其他应用问题,包括几何镶嵌、创建房屋数据模型、创建电场、分析基因数据、分析股票、创建卷积神经网络CNN)等。您将使用计算思维元素来解决问题,并为问题和数据集创建不同的图形和可视化表示。

要充分利用本书

您需要在计算机上安装 Python 3.9 才能运行代码。所有代码示例都在使用 Python 3.9 的 Windows 操作系统上进行了测试,并应该在后续版本中运行。所有代码还在 Anaconda 虚拟环境中进行了测试。

本书中使用的其他库和软件包包括以下内容:

  • NumPy

  • Scikit-Learn

  • TensorFlow/Keras

  • Matplotlib

  • Seaborn

  • Cairos

  • NLTK

  • Pandas

如果您想在 Spyder 环境或 Jupyter 笔记本中运行代码,可以安装 Anaconda,这是 Python 和 R 编程语言的环境管理器。

如果您使用的是本书的数字版本,我们建议您自己输入代码或通过 GitHub 存储库(链接在下一节中提供)访问代码。这样做将有助于避免与复制和粘贴代码相关的任何潜在错误。

下载示例代码文件

您可以从 GitHub 上下载本书的示例代码文件github.com/PacktPublishing/Applied-Computational-Thinking-with-Python。如果代码有更新,将在现有的 GitHub 存储库上进行更新。

我们还有来自我们丰富书籍和视频目录的其他代码包,可在github.com/PacktPublishing/上找到。去看看吧!

下载彩色图片

我们还提供了一个 PDF 文件,其中包含本书中使用的屏幕截图/图表的彩色图片。您可以在这里下载:static.packt-cdn.com/downloads/9781839219436_ColorImages.pdf

使用的约定

本书中使用了许多文本约定。

文本中的代码:表示文本中的代码词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 句柄。例如:"最后,我们要求程序使用Readability函数分析文本。请注意,我们将其保存到r中。"

代码块设置如下:

def encrypt(message, key):
    encryptedM = ''
    for letts in message:
        if letts in LETTERS:
            num = LETTERS.find(letts)
            num += key
            encryptedM +=  LETTERS[num]
    return encryptedM

任何命令行输入或输出都以以下方式编写:

There once was a citizen in the town of Narnia, whose name was Malena. Malena loved to hang with their trusty dog, King Kong.
You could always see them strolling through the market in the morning, wearing their favorite blue attire.

粗体:表示一个新术语,一个重要的词,或者屏幕上看到的词。例如,菜单或对话框中的单词会以这种方式出现在文本中。这是一个例子:“正如您从前面的截图中所看到的,死亡列继续上升,病例数量也在增加,我们稍后会在这个问题中再看一下。”

提示或重要说明

像这样出现。

第一部分:引言计算思维

在我们生活的世界中,我们在一天中不断地与代码互动,却没有意识到。当我们在网上搜索东西,使用智能手机,使用健身追踪器或智能手表,以及许多其他情况下,都涉及到算法。甚至我们的汽车也有我们每天都要与之互动的计算机。我们将从计算机科学的一些基本原理开始,看看编程和设计算法背后的东西,以及一些应用计算思维和 Python 编程语言的算法设计。

在本节中,您将清楚地了解计算机科学,计算思维的要素 - 即分解、模式识别、模式概括和抽象 - 以及算法设计。

本节包括以下章节:

  • [第一章],计算机科学基础

  • [第二章],计算思维要素

  • [第三章],理解算法和算法思维

  • [第四章],理解逻辑推理

  • [第五章],探索问题分析

  • [第六章],设计解决方案和解决方案过程

  • [第七章],识别解决方案中的挑战

第一章:计算机科学基础

计算机科学的世界是一个广阔而复杂的世界。它不仅不断变化和发展,而且我们认为是计算机科学的组成部分也在不断适应和调整。计算思维过程使我们能够有目的地和专注地解决任何问题。无论问题是什么,我们都可以将其分解,找到可以帮助我们找到解决方案的模式,概括我们的解决方案,并设计可以帮助我们提供解决方案的算法。

在本书中,我们将仔细研究计算思维过程,解决多个领域的问题,并使用 Python 编程语言及相关库和包来创建算法,帮助我们解决这些问题。然而,在进入问题之前,我们将探讨一些重要的计算机科学概念,这些概念将帮助我们导航本书的其余部分。

在本章中,我们将探讨以下主题:

  • 计算机科学简介

  • 理论计算机科学

  • 系统软件

  • 计算

  • 数据类型和结构

技术要求

您需要最新版本的 Python 来运行本章中的代码。您可以在此处找到本章使用的完整源代码:github.com/PacktPublishing/Applied-Computational-Thinking-with-Python/tree/master/Chapter01

计算机科学简介

当寻找计算机科学的定义时,您会遇到多种变体,但所有都指出计算机科学涵盖了计算机和计算概念的所有方面,包括硬件和软件。在计算机科学中,硬件设计主要在工程或计算机工程课程中学习。计算机科学的软件方面包括操作系统和应用程序等编程领域。在本书中,我们将集中在计算机科学的软件方面。

在本章中,我们将研究一些基本的定义、理论和系统,这些对我们深入计算思维世界至关重要。一旦我们确定了关键领域并定义了概念,我们就准备好进入应用和现实世界中面临的挑战,同时探索计算思维的要素和可以帮助我们应对这些挑战的 Python 编程能力。

计算机科学中提供的广泛主题范围既令人望而生畏又令人兴奋,它不断发展。其中一些主题包括游戏设计、操作系统、移动或桌面设备的应用程序、机器人编程等。计算机和计算领域的不断突破为我们提供了新的和令人兴奋的机会,其中许多是我们所不知道的。对计算机科学背后的系统有基本的了解可以帮助我们更有效地与技术互动并解决问题。

学习计算机和二进制系统

所有计算机都将信息存储为二进制数据。二进制系统将所有信息视为开关,可以是开或关,0 或 1。二进制系统是一个基于 2 的系统。您需要对二进制数字和二进制系统有基本的了解,才能在计算机科学中取得进展。

二进制系统将所有数据转换为字符串,只使用两个数字:0 和 1。数据在计算机中使用比特存储。比特(代表二进制数字)是计算机中可以找到的最小数据单元,即 0 或 1。

在二进制系统中计数时,前两个数字是 0(或 00)和 1(或 01),就像我们在日常生活中使用的十进制数系统一样。如果我们继续以二进制计数,下一个数字将是 10。在学习如何从一个系统转换为另一个系统之前,让我们比较一下十进制系统和二进制系统中的前三个数字:

图 1.1-十进制和二进制比较

图 1.1-十进制和二进制比较

十进制系统中的下一个数字将是 3。在二进制系统中,下一个数字将是 11,读作“一一”。十进制和二进制系统中的前 10 个数字如下所示:

图 1.2-十进制和二进制比较(续)

图 1.2-十进制和二进制比较(续)

如前所述,二进制系统是一个基于 2 的系统。这意味着十进制系统的每个数字都与 2 的幂配对,因此我们使用这些幂来在数字之间进行转换。了解如何从二进制转换为十进制,反之亦然,可以帮助我们更好地理解不同系统中数字之间的关系。

从二进制转换为十进制

我们将从一个示例开始,将二进制数转换为十进制数。取数字 101101。要转换该数字,每个数字都乘以相应的基数 2 的幂。给定的二进制数字有 6 位,因此我们将使用的 2 的幂是 5、4、3、2、1 和 0。这意味着数字转换如下:

二进制数 101101 相当于十进制系统中的 45。在日常生活中,我们以十进制书写数字,因此我们理解 45 这个数字的书写方式。然而,我们的计算机将这些信息转换为二进制以便能够处理它,因此该数字变成了二进制数 101101,以便计算机能够轻松读取。

从十进制转换为二进制

再次,让我们从一个示例开始,以演示从十进制数转换为二进制数的过程。取数字 591。要将十进制数转换为二进制数,我们必须迭代地将数字除以 2。如果结果没有余数,我们插入 0(如果是第一个数字)或在现有数字的左侧插入 0。

如果结果有余数 1,我们插入 1(如果是第一个数字)或在现有数字的左侧插入 1。

当我们将 591 除以 2 时,结果是 295,余数为 1。这意味着我们最右边的数字,也就是我们的第一个数字,是 1。

现在将 295 除以 2。结果是 147,余数为 1。因此,我们在 1 的左侧插入 1。我们的数字现在是 11。

现在将 147 除以 2。结果是 73,余数为 1。我们的结果现在是 111。现在我们将进行进一步的除法:

  • 余数为 1。我们的数字现在是 1111。

  • 没有余数。我们的数字现在是 01111。

  • 没有余数。我们的数字现在是 001111。

  • 余数为 1。我们的数字现在是 1001111。

  • 没有余数。我们的数字现在是 01001111。

  • 没有余数。我们的数字现在是 001001111。

  • 余数为 1。我们的数字现在是 1001001111。

十进制中的数字 591 相当于二进制系统中的数字 1001001111。

另一种转换数字的方法是使用表格进行除法:

表 1.1-将十进制数 591 转换为二进制

表 1.1-将十进制数 591 转换为二进制

使用表格,取最右边一列的数字,并从底部到顶部开始写下这些数字。结果是 1001001111。

学习如何转换数字只是将数据转换为二进制的一小部分,但它是一个重要的部分。所有信息,包括字母和符号,都必须转换为二进制才能被计算机读取。ASCII(代表美国信息交换标准代码)是一个被普遍采用的协议,用于转换信息。也就是说,该协议的一些部分已经过时,因此其他协议使用 ASCII 作为基础来扩展其功能。Unicode 是一个广泛使用的基于 ASCII 的 16 位字符集。

正如讨论的,在本节中,我们了解到信息必须被编码或转换才能被计算机读取。存在多个系统和协议,但现在我们将继续讨论计算机科学理论。然而,在解决问题时,回顾二进制、ASCII 和 Unicode 可能会有所帮助。

理解理论计算机科学

虽然你不需要成为数学大师才能喜欢计算机科学,但这两个学科是内在联系的。计算机科学,特别是编程,使用的是代数性质的算法。我们将在后面深入探讨算法,但重要的是它们是数学的。逻辑过程源自数学的哲学性质和历史。现在,如果数学主题不是你的菜,不要绝望。成为程序员和开发人员所需的逻辑过程可以在不学习更高数学的情况下使用。了解更高的数学只是为那些具有这方面背景的人简化了一些概念。

理论计算机科学包括多个理论和主题。以下列出了一些主题和理论,但请记住,理论计算机科学中还包括其他可能在本书中未讨论的主题。以下列出的每个理论或术语都包括简短的描述和解释供您审阅:

  • 算法

  • 编码理论

  • 计算生物学

  • 数据结构

  • 密码学

  • 信息理论

  • 机器学习

  • 自动机理论

  • 形式语言理论

  • 符号计算

  • 计算几何

  • 计算数论

我们将在接下来的章节中看一下上述的理论。

算法

算法是计算机可以读取的一组指令。算法以一种计算机可以逻辑处理提供的输入信息并创建输出的方式提供规则或指令。在大多数书籍中,你是通过创建Hello World!程序来介绍算法和编程的。我不会让这本书成为例外。

在 Python 中,代码需要打印消息到屏幕上。因为 Python 语言易学易读,许多代码都力求逻辑。因此,为了将消息打印到屏幕上,我们使用print()命令。以下是我们将使用的代码:

print("Hello world!")

同样,我们可以使用以下给出的代码:

print('Hello world!')

当涉及到字符串时,Python 将"'视为相同的东西。

当我们运行算法时,前面代码的结果如下截图所示:

图 1.3 – 'Hello World!' Python 程序

图 1.3 – 'Hello World!' Python 程序

别担心,我们将在第二章后面讨论 Python 编程语言,计算思维要素,以及在第二部分应用 Python 和计算思维中更深入地讨论,从第八章开始,Python 简介

尽管讨论算法很长,但对于本书和你学习 Python 的进展至关重要。因此,我们将在本书的第二章——计算思维要素第三章——理解算法和算法思维中深入探讨算法,因为算法是计算思维过程的关键要素。

重要提示:

第二章——计算思维要素将专注于计算思维过程本身,它有四个要素:分解模式识别、模式概括和抽象算法设计。正如你所看到的,最后一个要素是算法设计,因此我们需要更加了解算法是什么以及如何创建它们,这样你就可以在用 Python 解决问题时实施和设计算法。第三章——理解算法和算法思维将专注于更深入地理解算法定义以及设计过程的介绍。

接下来我们将看编码理论。

编码理论

编码理论有时也被称为代数编码理论。在处理代码和编码理论时,有三个研究领域:数据压缩错误校正密码学。我们将在接下来的章节中更详细地介绍这些内容。

数据压缩

数据压缩的重要性不容忽视。数据压缩使我们能够在占用最少空间的情况下存储最大量的信息。换句话说,数据压缩使用最少的位来存储数据。

重要提示:

请记住,比特是计算机中最小的数据单位,即 0 或 1,而一组比特称为字节。一个字节通常有 8 个比特。我们使用字节作为计算机内存、存储设备(如存储卡或外部驱动器)等大小的度量单位。

随着技术和存储容量的增长和改善,我们存储额外数据的能力也在增加。从历史上看,当计算机首次进入家庭时,它们通常只有千字节兆字节的存储空间,但现在它们拥有千兆字节太字节的存储空间。各种存储单位的换算如下所示:

图 1.4 – 字节转换

图 1.4 – 字节转换

如果你在网上寻找信息,可能会发现一些来源声称一太字节等于 1,024 千兆字节。这是一个二进制换算。在十进制系统或十进制系统中,一太字节等于 1,000 千兆字节。要更好地理解换算,重要的是要了解适用于十进制系统和二进制系统的前缀:

表 1.2 – 带值的十进制和二进制前缀

表 1.2 – 带值的十进制和二进制前缀

如前所述,目标始终是尽量使用最少的位来存储尽量多的数据。因此,我们压缩或减少数据的大小,以便使用更少的存储空间。

那么,数据压缩为什么如此重要呢?让我们回到 2000 年。那时,一台售价约 1000 美元的笔记本电脑大约有 64MB 的RAM(随机存取存储器)和 6GB 的硬盘内存。我们手机上的一张照片在使用其实际大小时需要 2 到 5 兆字节的内存。这意味着我们的计算机无法存储现在拍摄的许多(在某些情况下是任何)现代照片。数据压缩的进步使我们能够存储更多内存,创建更好的游戏和应用程序,以及更多,因为我们可以拥有更好的图形和额外的信息或代码,而无需太担心它们使用的内存量。

错误更正

在计算机科学中,错误是生活中的一个事实。我们在我们的过程、算法、设计和各种其他方面都会犯错误。错误更正,也称为错误处理,是计算机自动纠正错误或多个错误的过程,这种错误发生在数字数据被错误传输时。

错误更正码ECC)可以帮助我们分析数据传输。ECC 可以定位和纠正传输错误。在计算机中,ECC 内置在一个可以识别常见内部数据损坏问题的存储空间中。例如,ECC 可以帮助读取损坏的代码,比如QR(快速响应)代码的缺失部分。一种 ECC 是汉明码。汉明码是一种二进制线性码,可以检测最多两位错误。

重要说明:

汉明码是以 1950 年发现它们的理查德·韦斯利·汉明命名的。汉明是一位数学家,他在与电信和计算机工程相关的编码方面进行了研究。

另一种 ECC 是奇偶校验位。奇偶校验位检查数据的状态,并确定是否有任何数据丢失或被覆盖。错误更正对所有开发的软件都很重要,因为任何更新、更改或升级都可能导致整个程序或部分程序或软件的损坏。

密码学

密码学用于计算机科学中隐藏代码。在密码学中,信息或数据被编写成只有消息的预期接收者才能阅读的形式。简单来说,密码学将可读文本或信息转换为不可读的文本或信息。

当我们现在考虑密码学时,我们往往会想到数据的加密。编码者通过将数据转换为未经授权用户无法看到的代码来加密数据。然而,密码学已经存在了几个世纪,即它早于计算机。历史上,密码学的第一个用途可以追溯到公元前 1900 年左右的埃及一座墓穴中。在墓穴的各个部分,常见的象形文字与非典型或不寻常的象形文字混合在一起。

非典型的象形文字的原因是未知的,但这些信息被隐藏起来,不让其他人看到。后来,密码学被用于在战争和和平时期的政府和间谍之间进行秘密通信。如今,密码学被用于加密数据,因为我们的信息存在于数字格式中,因此保护敏感信息,如银行、人口统计或个人数据,是很重要的。

我们将通过本书中提出的一些问题进一步探讨编码理论的主题。

计算生物学

计算生物学是理论计算机科学的一个领域,专注于生物数据和生物信息学的研究。生物信息学是一门科学,允许我们收集生物数据并对其进行分析。生物信息学的一个例子是收集和分析基因密码。在生物学研究中,大量数据被探索和记录。

研究可以涉及各种各样的主题和跨学科。例如,遗传研究可能包括来自整个州、整个种族或整个国家的数据。计算生物学的一些领域包括分子、细胞、组织和生物体。计算生物学使我们能够研究这些事物的组成,从最基本的水平到更大的生物体。生物信息学和计算生物学为这些领域的实验研究提供了一个结构,创建了预测和比较,并提供了一种发展和测试理论的方式。

计算思维和编码使我们能够处理和分析这些数据。在本书中,所提出的问题将使我们能够探索如何使用 Python 结合计算思维来解决复杂问题,包括计算生物学中的问题。

数据结构

在编码理论中,我们使用数据结构来收集和组织数据。目标是准备数据,以便我们可以高效和有效地执行操作。数据结构可以是原始的或抽象的。软件具有内置的数据结构,这些是原始的数据结构,或者我们可以使用编程语言定义它们。原始数据结构是预定义的。一些原始数据结构包括整数、字符(char)和布尔结构。抽象或用户定义的数据结构的示例包括数组和二维数组、栈、树和二叉树、链表、队列等。

用户定义的数据结构具有不同的特征。例如,它们可以是线性的或非线性的,均匀的或非均匀的,静态的或动态的。如果我们需要将数据排列成线性序列,我们可以使用数组,这是一种线性数据结构。如果我们的数据不是线性的,我们可以使用非线性数据结构,例如图形。当我们有相似类型的数据时,我们使用均匀的数据结构。

请记住,例如,数组既是线性的又是均匀的数据结构。非均匀或异质数据结构具有不同的数据。用户可以创建的非均匀数据结构的一个例子是类。静态和动态数据结构的区别在于静态结构的大小是固定的,而动态结构的大小是灵活的。为了更好地理解数据结构,我们将通过本书中的计算思维元素来解决问题,再次简要地回顾数据结构,因为它们与数据类型有关,那时会讨论。

信息理论

信息理论被定义为一种数学研究,允许对信息进行编码,以便通过计算机电路或电信信道进行传输。信息通过可能包含符号、脉冲甚至无线电信号的序列进行传输。

在信息理论中,计算机科学家研究信息的量化、数据存储和信息通信。信息在信息理论中可以是模拟的或数字的。模拟数据指的是由模拟信号表示的信息。反过来,模拟信号是随着给定时间段的连续波动。数字信号将数据显示为二进制,即离散波。我们将模拟波表示为正弦波,将数字波表示为方波。以下图显示了正弦曲线作为时间值的函数:

![图 1.5 – 模拟信号(图像/Figure_01.05_B15413.jpg)图 1.5 – 模拟信号模拟信号由正弦波的关键元素描述:振幅、周期、频率和相移:+ 振幅是曲线从中心到其高度的高度。正弦曲线无限重复。+ 周期指的是正弦曲线的一个周期的长度,即曲线在开始重复之前的长度。+ 正弦曲线的频率和周期呈反比关系:

与反比关系相关,我们也可以说:

  • 正弦曲线的相移是曲线从 0 处的偏移量。这在下图中显示:

图 1.6 - 相移示例

图 1.6 - 相移示例

相比之下,数字信号图看起来像条形图或直方图。它们只有两个数据点,0 或 1,所以它们看起来像是方盒状的山丘和山谷:

图 1.7 - 数字信号

图 1.7 - 数字信号

数字信号具有有限的离散数据集。数据集是离散的,因为它包含单独和不同的数据点。对于模拟信号,数据是连续的和无限的。在计算机科学中,这两种类型的信号都很重要和有用。我们将在本书的后续问题中探索数字信号,并特别在第十六章中,高级应用计算思维问题中探索数字信号的应用。

自动机理论

自动机理论是理论计算机科学中最迷人的话题之一。它涉及研究机器以及如何以最可靠和高效的方式完成计算。自动机理论涉及简单机器的物理方面以及逻辑处理。那么,自动机到底用于什么,它是如何工作的呢?

自动机是使用预定条件来响应外部输入的设备。当你看你的恒温器时,你正在使用一个自动机。你设置你想要的温度,恒温器会对外部来源进行信息收集并相应地调整温度。

自动机的另一个例子是外科手术机器人。这些机器人可以改善患者的手术结果,并不断得到改进。由于自动机理论的目标是制造可靠和高效的机器,它是人工智能和智能机器(如外科手术机器人)发展中的关键部分。

形式语言理论

形式语言理论在计算机科学中通常与自动机理论联系在一起。形式语言是研究语法、语法、词汇和一切涉及形式语言的学科。在计算机科学中,形式语言指的是计算机编程语言的逻辑处理和语法。关于自动机,机器处理形式语言以执行为其提供的任务或代码。

符号计算

符号计算是计算数学的一个分支,涉及计算代数。术语符号计算计算代数有时可以互换使用。一些编程软件和语言专注于数学公式的符号计算。使用符号计算的程序执行诸如多项式因式分解、简化代数函数或表达式、找到多项式的最大公约数等操作。

在本书中,当解决一些现实世界中出现的问题时,我们将使用计算机代数和符号计算。Python 允许我们不仅执行可能需要的数学计算,还可以探索由这些计算产生的图形表示或模型。当我们探索解决现实世界的问题时,我们将需要使用 Python 编程语言的各种库或扩展。在本书的第二部分应用 Python 和计算思维中,我们将更详细地探索 Python 编程语言。

计算几何

像符号计算一样,计算几何存在于处理计算数学的计算机科学分支中。我们在计算几何中研究的算法是可以用几何表达的。对数据的分析是通过几何图形,几何分析,遵循几何模式的数据结构等进行的。需要计算几何的问题的输入和输出是几何的。

在考虑几何时,我们经常会回到我们最常与数学分支相关联的图形,如多边形、三角形和圆。也就是说,当我们看计算几何时,一些算法是可以用点、线、其他几何图形或遵循几何模式的算法来表达的。三角测量属于计算机科学的这一分支。

数据的三角测量对于光学 3D 测量系统等应用非常重要。我们三角测量 GPS 信号来定位手机,例如,这在执法中使用。

在现代,三角测量有许多用途,其中一些我们将通过本书中提出的真实和相关问题进行探讨。

计算数论

数论是研究整数及其性质的数学分支。计算数论是用于解决数论问题的算法的研究。数论研究的一部分是素性测试。

用于确定输入或输出是否为素数的算法已被用于许多目的。素性测试和数论的一个最重要的用途和应用是用于加密目的。随着我们的生活转移到电子保存,我们最个人的信息,如银行信息、家庭信息,甚至社会安全号码,都存在某种代码或算法中。重要的是加密这样的信息,以便他人无法使用或访问。计算数论和密码学是内在联系的,您稍后将能够探索。

一些理论旨在帮助您了解计算机科学理论是如何交织在一起的,它们的应用以及它们与我们每天所做的工作的相关性。

在这一部分,我们学习了关于理论计算机科学的知识。我们还学习了它的各种理论。

在本书中,我们将使用计算思维(在第二章中进一步讨论,计算思维的要素)来帮助我们解决问题,从最基本的应用到一些复杂的分析,通过定义和设计使用这些理论的适当算法。理论计算机科学用于研究系统软件,我们将在下一节中探讨。

学习系统软件

系统软件用于执行多个功能,并在计算机的操作系统OS)、外围设备(如键盘和鼠标)和固件之间进行通信。固件是永久保存在设备上并且需要用于其操作的软件,这些是两种主要类型的软件的一部分:系统软件应用软件

系统软件允许计算机在硬件和应用程序之间进行通信。想想智能手机。手机在其最基本的形式中由硬件组成,包括电池、相机、内存、屏幕和所有物理组件和外围设备。操作系统允许应用程序使用这些组件。

手机的相机应用程序。系统软件让应用程序与手机通信,使用相机拍照,编辑,保存和分享照片。计算机的操作系统也允许硬件与程序通信。设计程序将使用鼠标或其他外围设备来绘制,创建,使用触摸屏(如果可用)等。

如果我们不了解系统的软件,就无法创建能够有效与我们的硬件通信的应用程序,从而产生从关键错误到使外围设备无用的错误,或者是一些组件可能工作,比如拍照,但其他可能不工作,比如保存或分享照片的次要错误。系统软件是以一种方式创建的,以便为我们提供在硬件和应用程序之间进行最简单、最有效的通信方式。

操作系统

操作系统执行多个任务。如果您回忆一下,错误处理是操作系统的一部分,它检查最常见的可能错误,以便在不造成更大问题或使应用程序变得毫无价值的情况下修复它们。错误处理是操作系统最重要的任务之一。此外,操作系统负责计算机或设备的安全。如果您有智能手机,您会知道许多操作系统的更新是为了解决安全问题或防止安全漏洞。操作系统负责只允许授权用户与设备中存储的内容进行交互。

除了安全和错误处理,操作系统还负责为文件分配内存并对其进行组织。当我们保存和删除文件或程序时,之前使用的内存现在是空闲的。但是,可能会有一些东西在之前和之后立即保存。操作系统通过分配和重新分配内存来维护设备的最佳性能。内存管理不仅指用户保存的文件,还指 RAM。

设备的文件管理也由操作系统运行。操作系统将信息分配为文件系统,将信息分成用户和设备轻松访问的目录。文件系统负责跟踪文件的位置,包括来自操作系统和用户的文件,不断发展的设备访问设置,以及如何访问文件和了解文件状态。近年来,对设备的访问方式发生了变化。

虽然计算机通常使用用户名和密码,但现在许多设备可以通过指纹、数字或字母数字密码、面部识别、图像、路径等进行访问。随着这些主题的发展,操作系统也需要更新或重新创建。操作系统还负责允许应用程序与设备之间的通信。

应用软件

应用软件是指执行特定任务的软件应用程序。想想你可以从移动设备访问的应用程序或应用。有数百种类型的应用程序,例如设备上的静态游戏,允许您远程与他人玩游戏的游戏,新闻应用程序,电子书阅读器,健身训练应用程序,闹钟,时钟,音乐等等!应用程序总是执行某种形式的任务,无论是个人使用、商业使用还是教育使用。

应用软件具有多个功能。您可能会发现用于生产力的套件,例如MicrosoftOffice)和Google产品。当我们需要在互联网上进行研究时,我们使用称为浏览器的应用程序,它们允许我们访问信息并对信息进行索引,以便我们可以访问它。这些浏览器包括Google ChromeSafariFirefoxEdgeOpera等。浏览器既可用于移动设备,也可用于计算机。请记住,应用程序的目的是为最终用户执行特定任务。

重要提示:

顺便说一句,自从计算机成为家庭工具并且手机开始被用于除了打电话之外的其他用途以来,应用程序已经呈指数级增长。早期的计算机只用于计算数学分析和任务。这就是为什么了解计算机科学的发展和历史如此重要的原因之一。由于我们无法完全预测计算机科学和系统软件的未来用途,我们对它们了解得越多,就越能在技术进步发生时创造和适应。

在本节中,我们了解了系统软件。我们还了解了操作系统软件和应用软件。对于本书的目的,一些应用程序将更为重要,因为我们在解决一些问题时需要排序,例如数据库、生产力软件、企业资源规划和教育软件。

在下一节中,我们将学习计算。

理解计算

在计算机科学中,计算指的是计算机执行以进行通信、管理和处理信息的活动。计算通常分为四个主要领域:算法架构编程语言理论

由于我们在之前的部分中已经讨论了理论和算法,现在我们将专注于定义架构和编程语言。

架构

计算机架构指的是与计算机系统交互的指令集。更基本的说,架构包括允许软件和硬件交互的指令。计算机架构有三个主要的子类别:指令集架构ISA)、微架构系统设计

指令集架构(ISA)

ISA 是硬件和软件之间存在的边界。它以多种方式分类,但两种常见的是复杂指令集计算机CISC)和精简指令集计算机RISC)。它们的定义如下:

  • CISC:这是一种具有许多任务的显式指令的计算机,例如简单的数学运算和从内存加载内容。CISC 包括 RISC 中不包括的所有内容。

  • RISC:这是一种具有减少每条指令周期CPI)的架构的计算机。

CISC 试图用更少的步骤完成指令,而 RISC 只使用简单的指令。CISC 是多步的,而 RISC 是单步的,一次执行一个任务。CISC 过程包括指令、微码转换、微指令和执行。相比之下,RISC 包括指令和执行。

在 CISC 中,微码转换指的是在较低级别解释语言。它考虑硬件资源来创建微指令。微指令是微码中的单个指令。微码创建微指令后,可以执行微指令。以下图表显示了 RISC 和 CISC 的过程:

图 1.8 - RISC 和 CISC 之间的差异

图 1.8 - RISC 和 CISC 之间的差异

RISC 和 CISC 对计算机程序员来说都是必不可少的。单步过程(RISC)与多步过程(CISC)相比有优势和劣势。RISC 减少了每条指令的周期,一次只做一件事。CISC 减少了程序中的指令,但以每条指令的周期为代价。根据我们的需求,我们可以选择最佳路径。

编程语言

编程语言是我们为计算机和其他设备编写指令的方式。根据需要、易用性等因素,使用不同的语言。编程语言的例子包括以下内容:

  • Ruby 和 Python:Ruby 是主要用于 Web 应用程序的编程语言。Ruby 稳定且易于使用;然而,许多开发人员选择使用 Python 而不是 Ruby,因为 Python 更快。尽管 Ruby 并不像 Python 那样受欢迎并且存在一些性能问题,但该语言在 2019 年仍然非常活跃并持续增长。另一方面,Python 被广泛用于多种用途,如 Web 应用程序、用户界面应用程序和网站等。我们将在本书的后面更深入地探讨 Python。

  • C:C 语言是计算机科学的一个非常重要的部分,因为 C 是第一种使用的语言,仍然是最广泛使用的语言。C 自 1972 年以来一直存在,当时 Dennis Ritchie 发明了它,但自 1978 年首次发布以来就被其他人使用。尽管其他语言自那时以来变得更受欢迎,但 C 在 2019 年仍在使用。它的一些用途包括操作系统、硬件驱动程序和应用程序等。C 是一种基本级别的语言,这意味着它几乎不需要抽象。

  • C++:C++是由 Bjarne Stroustrup 于 1985 年作为 C 的扩展开发的。该语言的目标是添加面向对象的能力。该语言仍然广泛用于操作系统和其他软件中与 C 语言一起使用。C++是一种中级编程语言。

  • C#:C#(C sharp)是一种高级编程语言。与 C++类似,它具有面向对象的能力,并且是 C 编程语言的扩展。C++和 C#之间的主要区别之一是 C++使用机器代码,而 C#使用字节码。机器代码可以直接由计算机执行。字节码必须被编译,因此被认为是需要解释的低级代码。

  • Swift:Swift 编程语言是由苹果公司于 2014 年开发的。就编程语言而言,Swift 是最新的之一。苹果在 2015 年发布了2.2 版本作为开源编程语言。该语言被认为是一种通用和编译的编程语言。

  • Scratch:Scratch 于 2002 年由麻省理工学院媒体实验室开发为一种视觉编程、块编码语言。作为一种块编程语言,它在学校中被广泛用于教授各个年龄段的学生如何编码。Scratch 现在适用于多种用途,包括一些机器人应用程序,如 Vex Code,结合了机器学习和人工智能等。它与Makey Makey等流行的课堂外设兼容,这是一种与计算机交互并可以完全由 Scratch 程序控制的电路。虽然它在教育目的上很受欢迎,但编程语言的强大和其功能本身仍在不断增长。

  • Java 和 JavaScript:JavaScript 是一种仅在浏览器中使用的脚本语言。它用于制作网站和 Web 应用程序。另一方面,Java 是一种通用编程语言。JavaScript 帮助我们使网站动画化或为其添加交互功能。相比之下,Java 被编译为字节码,并且广泛用于开发 Android 设备和应用程序。

  • PHP:PHP 又称为超文本预处理器。与 Java 类似,它是一种通用编程语言。它是开源的,因此广泛可用。PHP 用于网站设计和应用程序,并被认为易于学习,但具有许多高级功能。PHP 也可以用于编写桌面应用程序。

  • SQL:SQL,或结构化查询语言,是一种用于与数据交互的编程语言。SQL 是特定领域的。它自 1974 年首次出现以来几乎与 C 一样重要。SQL 的主要重要性在于它可以与数据库交互,而其他语言无法做到这一点。

在计算思维中,我们使用许多不同的编程语言,这取决于我们的目标是什么,我们有或需要什么信息,以及我们的应用程序或软件要求是什么。选择一种语言不仅取决于我们对语言的了解,还取决于语言的可能功能。

由于 Python 具有开放源代码的特性、易于使用以及可以用于大量应用程序,我们将在本书中更广泛地使用 Python。然而,Python 并不是唯一的选择。了解其他语言也很重要,特别是对于开发人员来说。

在本节中,我们学习了计算和它的一些领域,即架构和编程语言。我们还学习了 ISA 及其类型,以及对各种编程语言的介绍。在下一节中,我们将看看数据类型和结构。

学习数据类型和结构

在计算机科学中,数据类型和结构是两个不同的东西:

  • 数据类型是一种基本分类。一些数据类型包括整数、浮点数和字符串。

  • 数据结构使用多种数据类型。它们可以将信息组织到内存中,并确定我们如何访问信息。

让我们在接下来的部分更详细地看一下这些。

数据类型

如前所述,数据类型是基本分类。它们是程序中使用的变量,只能存在一个分类。有不同类的数据类型。我们现在将重点关注原始抽象数据类型,但随着我们解决问题和设计解决方案,我们将重新讨论这个话题。

原始数据类型包括字节短整型整型长整型浮点型双精度浮点型布尔型字符型

  • 一个字节可以存储-128 到 127 的数字。虽然这些数字可以存储为整数,或int,但字节使用的存储空间更少,所以如果我们知道数字在这些值之间,我们可以使用字节数据类型。

  • 短整型是-32,768 到 32,767 之间的数字。

  • 整数int用于存储-2,147,483,648 到 2,147,483,647 之间的数字。

  • 长整型用于存储-9,223,372,036,854,775,808 到 9,223,372,036,854,775,807 的数字。

  • 浮点数允许我们保存小数。

  • 十进制数也可以保存为双精度浮点型,它比浮点数具有更高的精度。

  • 布尔值是数据类型,要么是True,要么是False。因此,可以保存变量,以便在打印其值时,结果将保存为真或假。

  • 字符用于将变量保存为单个字符。

我们将在下一节中研究数据结构。

数据结构

正如本章前面的编码理论部分所述,数据结构用于以最有效和最有效的方式收集和组织数据。数据结构可以是原始的,例如软件中的内置数据结构,也可以是抽象的。原始数据结构也可以使用编程语言定义,但它们是预定义的。一些原始数据结构包括前一节中列出的数据类型,如字符布尔结构。

抽象数据类型ADTs)包括数据类型的结构和设计信息。抽象数据结构包括数组和二维数组、栈、树和二叉树、链表、队列等,正如本章前面的编码理论部分所述。列表可以包含相同数据值的多个实例。这些列表是可计数的,因此我们可以找到列表中有多少元素,重新排序它们,删除项目,添加项目等。列表广泛用作链表、数组或动态数组:

  • 链表意味着列表中的每个数据元素都连接或指向下一个数据元素,无论它们存储在内存的何处。

  • 数组是有序的。元素按顺序读取以便理解。把数组想象成阅读这个句子。你不会把句子读成“数组一个思考阅读作为这的句子”。我们按顺序从左到右读句子,而不是混乱的顺序。

  • 动态数组可以调整大小,这在选择数据类型时很重要。

ADT 是元素的集合,有两个操作 - 推入和弹出。推入用于向集合中添加元素,而弹出则移除最近的元素。

队列 ADT 是一种线性数据结构。与栈类似,我们可以添加或移除元素。但是,在队列 ADT 中,删除和插入的点在两个不同的端点进行。

如前所述,数据结构是数据类型的具体实现。例如,我们如何向集合中添加或移除元素就是数据结构。

这可能有点令人困惑,但我们将通过后面章节中的示例来更多地了解它们。目前,理解定义和简单示例就足够了。

总结

本章我们学习了一些计算机科学的基础知识。我们学习了如何将二进制转换为十进制。我们还探讨了理论计算机科学中的主题和理论。我们学习了关于计算和数据类型以及数据结构的知识。这些部分将帮助我们理解计算思维过程以及如何解决各种问题,从本书的第二章《计算思维要素》开始。

随着我们深入研究计算思维世界和过程,我们需要重新审视本章的一些内容,因为我们要解决问题,寻找最佳解决方案,并决定如何编写算法。

问题可能有无数种使用算法解决的方法。了解过程如何工作以及哪种数据结构最适合我们的问题对于创建最佳解决方案至关重要。确定算法所需的数据类型以及计算机如何读取数据只会帮助我们编写最有效和高效的算法。

在下一章中,我们将学习计算思维过程以及如何分解问题以设计我们的算法解决方案。

第二章:计算思维的元素

上一章提供了关于计算机科学基础的一些一般信息。在本章中,我们将更专注于理解计算思维和构成计算思维的元素:分解、模式识别、模式泛化或抽象以及算法设计。

计算思维过程的重要性不可低估。通过这个过程,我们分析问题,设计基于一组条件的最佳解决方案。虽然可以编写许多算法来回答相同的问题,但使用计算思维过程可以帮助我们确定设计清晰算法的最佳路径。

在本章中,我们将涵盖以下主题:

  • 理解计算思维

  • 分解问题

  • 识别模式

  • 泛化模式

  • 设计算法

  • 额外的问题

为了更多地了解计算思维,我们将通过一个提出的问题来看待这些元素。请记住,在第八章中,Python 简介中,我们将深入研究 Python 编程语言,但在您深入研究本书的本章和其他章节时,您可以随时来回查阅该章节。

技术要求

您需要最新版本的 Python 来运行本章中的代码。您可以在此处找到本章中使用的完整源代码:github.com/PacktPublishing/Applied-Computational-Thinking-with-Python/tree/master/Chapter02

理解计算思维

在其最基本的定义中,计算思维是一个解决问题的过程。就像设计思维、科学方法和其他类似的方法一样,我们经历一系列步骤来找到解决方案。例如,科学方法有七个步骤。请记住,关于科学方法存在多种解释,有些在步骤数量上有所不同。在本讨论中,我们将使用以下七个步骤:

  1. 问题

  2. 假设

  3. 材料

  4. 实验

  5. 结果

  6. 结论

  7. 发现的沟通

科学方法的确立是一个备受争议的话题,但大多数研究人员都同意它可以追溯到 10 世纪。

科学方法使我们能够观察自然界,提出假设来测试观察,并通过一个既定的过程开发测试和结果。这种方法本身在柏拉图和亚里士多德等哲学家的基础上有一定的依据,他们提倡经验研究。然而,他们的方法论不如今天我们所说的科学方法发展得那么成熟。

计算思维的元素类似于科学方法。计算思维使用较少的步骤来解决与编程相关的问题,而科学方法用于实验。通过计算思维,我们泛化算法,而在科学方法中,我们能够重现结果并从样本泛化到总体得出结论。

在现代,我们根据我们追求的研究领域和我们开发的技术,发展了其他方法论。其中两个例子是设计思维过程和计算思维。

设计思维有五个步骤或阶段:

  1. 共情

  2. 定义

  3. 构思

  4. 原型

  5. 测试

我们使用上述阶段来理解客户、班级、问题、情况或其他需要解决的情况的需求。与用户的需求产生共鸣有助于我们确定和定义问题。构思和原型阶段是我们创建可能解决方案的地方。测试可能的解决方案是找到最佳解决方案的下一个必要步骤。在所有阶段之后,如果需要,我们可以重新进行循环,因为设计思维过程的目标不是完美,所以总是可以进行额外的工作。设计思维的目标是为明确定义的问题提供一个有效和可行的解决方案。这不是唯一可行的解决方案,也不是完美的解决方案。

在计算思维中,我们使用了一个具有四个元素的类似过程:

  1. 分解

  2. 模式识别

  3. 抽象

  4. 算法设计

与设计思维过程一样,计算思维中的问题并不清晰。这些问题有时被称为不明确的。我们被呈现了一系列情况,我们在开始构思或根据我们可以看到的模式创建可能的解决方案之前,我们定义或分解该问题。当我们考虑计算思维过程时,我们真正做的是尝试弄清楚如何让计算机按照一系列步骤解决我们所面临的问题。

让我们来看一个简单的计算思维问题。

问题 1 - 条件

让我们想象一下,广播电台的抽奖活动让参赛者选择两种可能的获奖方式:250 美元现金或以 25 美分的高度。

计算思维问题可能像问题 1那样模糊,甚至没有提出问题。你被给定了一系列条件,你的工作是确定问题是什么,并为你自己定义的问题找到解决方案。如果你仔细想想,这个问题没有完美的答案,但你可以创造条件来确定哪个选项确实是最好的,这取决于参赛者的身高。

为了分解这个问题,我们需要看看陈述了什么,并考虑没有陈述的内容。我们需要规则。

简而言之,赢家将选择货币支付:要么是 250 美元现金,要么是相当于他们身高的 25 美分。这些都是陈述的。但未陈述的内容也很重要:

  • 抽奖的时间表是什么? 有多少个赢家?

  • 我们是否想要在每个参赛者选择后跟踪我们花了多少?

  • 我们是否想要使用一个比较基准?

可能会想到其他事情,但现在让我们坚持这些问题。我们将假设抽奖没有固定的开始或结束日期,电台可能会在某一天选择多个赢家,也可能根本不选择。这些是我们在找出模式、概括它们并设计算法时要考虑的一些因素。

关于支付的所有信息,我们仍然没有办法弄清楚何时支付更多。选择 250 美元最好吗? 还是选择以 25 美分的高度最好? 我们能否创建一个算法,告诉我们哪个选项更好? 是的,我们可以创建一个解决整个问题的算法。

这个问题的模式总是相同的:现金价值和 25 美分的高度是固定的,所以我们总是可以使用数学来计算根据某人的身高,25 美分的高度转换成多少钱。

如果我们知道一些东西,我们可以清楚地陈述基于每个选择的赢得。这包括现金选择或以 25 美分的高度选择。如果选择以 25 美分的高度,我们需要以下内容:

  • 参赛者的身高

  • 一美分的厚度

接下来发生的事情是模式抽象的一部分。我们不知道每个参赛者的选择,但我们可以提前找出每个四分之一的厚度。这将在我们的算法中稍后需要。每个四分之一大约是 0.069 英寸,或 1.75 毫米厚。

重要提示:

在本书中,我们将使用惯例测量,例如英尺和英寸。然而,我们也会提供公制测量的算法。

观察我们的问题,我们可以用两种方式陈述赢得的方式。以下表达包括了以四分之一赢得的高度的数学算法。它们展示了确定参赛者身高后确定总赢得所需的步骤。

请注意,为了使用惯例算法,身高需要以惯例单位给出。为了使用公制算法,身高需要以公制单位给出。如果参赛者选择现金,那么总赢得就是$250。如果参赛者选择以四分之一身高,那么惯例单位和公制单位的算法如下:

  • 总赢得(惯例):

  • 总赢得(公制):

我喜欢不高风险的赌博。所以,我要说我想测试一下,用我的身高。所以我选择找出我的身高是多少个四分之一。我身高 5'10"。让我们算一下这是多少英寸。因为一英尺有 12 英寸,总身高的算法如下所示:

但我说我是 5'10",所以我们需要加上那 10 英寸:

现在,让我们使用我们之前定义的数学算法,,来找出我能赢得多少:

我使用了符号而不是=,因为表示这是一个近似值。由于我四舍五入了,我想确保我显示的是最佳近似值,而不是精确数字。

也许你现在已经解决了这个问题,但在计算思维中,我们必须经过抽象并设计一个适用于所有情况的算法。我们可以创建一个非常简单的程序,从用户那里获得简单的输入,或者我们可以创建一个更复杂的程序,不仅提供基本的总数,还可能提供总和、图表或我们发现与我们的情景相关的其他任何内容,并适用于所有情况。

一旦我们更深入地了解了计算思维过程的每个部分,我们将更多地设计这些算法。我们甚至会回到这个问题,展示如何为计算机创建运行的算法。我们可以创建一个算法,让我们使用某人的身高来决定使用哪种赢得。

或者,如前所述,我们可以写一个以$250 作为每个参赛者的赢得基准的基线,然后输入每个参赛者选择的内容,以便查看我们是否低于或高于$250 的基准。我们可以汇总这些结果,也就是继续将它们相加,以查看当电台停止抽奖时我们最终会得到什么结果。如果参赛者在电台进行抽奖的时间越长选择不同,我们甚至可以有一个图表显示我们的情况,等等。

在本节中,我们学习了计算思维的要素。我们还研究了一个计算思维问题。

然而,现在让我们继续深入研究计算思维的要素——即分解、模式识别、模式概括和抽象以及算法设计。

分解问题

分解是分解数据的过程。它可以包括解决问题所需的一系列过程或步骤。通过分解问题,我们可以在概括模式之前识别问题的组成部分或较小部分。

通过分解,我们可以识别和解决一个案例,然后将这些步骤概括到问题的所有可能实例中。为了真正理解分解,我们需要回到我们之前提出的问题,简单地说,就是问:如果我用硬币换取我的身高,会得到更多的钱吗,还是应该选择 250 美元?我们可以说我们想知道一个实例,并且数学上解决这个问题一次,比如只解决我的身高的问题。然而,我们可能需要其他实例的信息。我们可以创建一个程序,只是确定哪个选项,250 美元还是你的身高的硬币,会更好。或者我们可以考虑以下一些情况,这将意味着不同的算法:

  • 我们可以检查给定身高的选项,但也将每个项目添加到列表中,以便跟踪所有决策。

  • 我们还可能需要数组和该列表中元素的总和来跟踪比赛中的支出。

  • 我们还可以将总和与基线进行比较,以 250 美元作为每个个体的基准。

  • 我们还可以使用所有的元素,比如列表、总和、比较和可视化图形显示来更好地理解和跟踪结果。

正如你所看到的,算法将取决于我们确切想要跟踪或回答这个问题。我们的算法可以是一个简单的是或否类型的问题,我们只需检查哪个选项最好,或者它可以是一个更健壮的算法,带有数据和数据跟踪的可视化表示。现在让我们来看看我们如何在问题中寻找模式。

识别模式

模式识别是在我们分解问题后找到相似之处或模式的过程。在问题 1中,我们展示了一个问题,其中参赛者可以赢得 250 美元,或选择用他们的身高来换取硬币。对于每个参赛者来说都是一样的。唯一的区别是总价值取决于个人的身高。

在本节中,让我们看一个不同的问题,以更好地理解模式识别。

问题 2 - 数学算法和概括

想象一下,你正在为一个足球队准备聚会。这是一个社区球队,所以总是有 12 到 15 个孩子过来。你想要订购你需要的食物。你知道你将从你将使用的餐饮公司那里支付 12 美元每个孩子。现在,让我们分解这个问题:

  • 分解:我知道我们有 12 到 15 个孩子。我们也知道每个孩子的费用是 12 美元。我们的问题可以被看作是一个问题:我们如何估算费用?

  • 模式识别:你知道孩子的数量k在 12 到 15 之间。你知道每个孩子的费用是 12 美元。例如,如果我有 5 个孩子,费用将是

  • 模式概括:孩子的数量是未知的,所以我们将使用变量k表示未知值。这样,无论我们有多少孩子,我们都可以找出总费用。我们正在从一个案例,5 个孩子,概括到所有案例,k个孩子。

  • 算法设计:我们现在将编写数学算法。我们将在第三章中更多地使用可编程算法,理解算法和算法思维。我们也将在那里重新讨论这些数学算法。总费用将由方程给出,其中T是总费用,k是孩子的数量。

正如您从前面的问题中所看到的,模式识别对于找到一个概括的模式并编写我们的算法是很重要的。现在,让我们更仔细地看一下模式概括。

概括模式

一旦我们认识到我们的模式,我们需要进行模式概括抽象。也就是说,我们希望确保我们提出的解决方案可以用于我们已经确定的问题的多个实例。模式概括可以是一些简单的东西,比如编写一个基本的线性数学算法,就像我们为派对的成本所做的那样,其中每个孩子的成本是 12 美元。因此,任何数量k的孩子的成本将由 12k给出。但模式概括可能远不止于此。

如果我们回到问题 1,在那里你可以选择 250 美元,或者你可以选择用 25 美分的硬币来换算你的身高,我们的模式概括将允许我们检查任何人的身高与 250 美元的对比,以确定选择现金选项还是选择硬币哪个能让你得到更多的钱。

抽象让我们专注于我们需要的东西,并丢弃我们不需要的东西,以便为我们的问题创建最佳算法。现在,根据我们决定需要什么,我们可以添加或删除一些条件。

例如,如果我是一个参赛者,我只想知道哪个选项给了我更多的钱。我不关心总胜利,谁选择了 250 美元,谁选择了身高等等。但如果我是广播电台,我可能想知道总和,与基准的比较,以及更多。我将不得不选择基准,甚至可能以图形方式显示随时间发生了什么。这都是抽象过程的一部分。当您解决计算思维问题时,您还在确定对您的解决方案和算法重要和不重要的事情。

在这个问题的最简单形式中,如果你是一个参赛者,你想知道你赢得奖金的最佳可能情况是什么。如果你选择了 250 美元,但你的身高使得你的身高以 25 美分的硬币来换算的金额超过了 250 美元,你会想知道。如果你在广播电台工作,你可能想追踪不仅仅是每个获胜者的情况。抽象允许您适应所有情况,从解决一个数学问题到创建一个可以跟踪所有参赛者的所有选择的算法。现在让我们看看如何创建这些算法。

设计算法

正如本章中先前提到的,算法是一组指令。当我们编写计算机程序时,算法是一组指令,提供给计算机,以便它可以为所提出的问题提供解决方案。到目前为止,我们一直坚持数学算法,只是因为我们还没有深入研究 Python。然而,我们现在将一些算法转化为 Python 编程。

首先,让我们来看一下问题 1。在这里,我们有一个情况,你可以赢得 250 美元,或者你的身高以 25 美分的硬币来换算。假设是你在竞争,你会想知道哪个选项能让你赢得最多的奖金。

让我们再次看一下本章前面的数学算法:

  • 总获胜金额(习惯单位):

  • 总获胜金额(度量单位):

请记住,如果您使用的是习惯单位的身高,您将使用第一个算法。如果您使用的是公制单位,您将需要相应地调整程序。

当我们编程时,我们需要定义我们的变量。在这种情况下,h是我们用于身高的变量。但想想看;如果你是一个成年人,你的身高可能不会改变,但为了论证的目的,我们将假设它不会总是相同。因此,我们需要想知道哪个选项更好,250 美元还是用 25 美分的硬币来换算他们的身高的人输入他们的身高,以便程序会为他们提供答案。

输入是用户可以输入的内容。因此,当我们定义变量时,我们将要求输入。在 Python 和任何其他语言中,一个好的做法不仅仅是要求输入而没有任何指导。也就是说,我们希望告诉用户他们正在回答的问题。例如,我可以编写以下代码来要求用户输入他们的身高:

h = input("Enter your height in inches: ")

上述代码将要求用户输入一些内容。它还要求用户以英寸输入信息。如果您使用的是公制单位,您将相应说明。

我们还将信息保存为变量h。但我们还没有对该变量做任何操作。

我们可以进行基本的数学运算,并根据身高打印出我们得到的值:

h = input("Enter your height in inches: ")
total = (int(h)/0.069)*0.25
print(total)

请注意,在上面的片段中,我们在total变量的定义中使用了int(h)。我们将h值转换为整数,以便可以使用该变量进行数学运算。当我们要求输入时,变量被保存为字符串,这就是为什么我们需要转换它以便使用它。

使用我的身高运行上述代码,即 70 英寸,得到以下结果:

253.62318840579707

如果我们将打印代码调整如下所示,我们的答案将是253.62,看起来会好得多:

h=input("Enter your height in inches: ")
total = (int(h)/0.069)*0.25
print(round(total,2))

当我运行这个程序时,窗口看起来是这样的:

图 2.1 – Python 程序输出

图 2.1 – Python 程序输出

但有时我们希望代码做更多的事情。让我们删除那个print命令并创建一些条件。在接下来的几行中,我们将使用提供的值进行一些比较。例如,我们可以要求计算机为我们检查一些事情。有三种可能性:

  • 我们的身高可能会产生与 250 美元完全相同的结果。

  • 我们的身高可能会产生少于 250 美元的结果。

  • 我们的身高可能会产生超过 250 美元的结果。

现在,我将要求计算机根据这些条件告诉我该做什么。我们将需要一个if-elifelse语句。这些是我们将测试的条件,以便获得更好的输出。我们将测试总额是否与 250 美元相同。否则,如果总额少于 250 美元,我们将希望计算机执行某些操作(这是我们的elif语句)。最后,在所有其他情况下,我们将使用else命令:

h=input("Enter your height in inches: ")
total = (int(h)/0.069)*0.25
total = round(total,2)
if total == 250:
    print("Your height in quarters is the same as $250.")
elif total > 250:
    total = str(total)
    print("Your height in quarters is more than $250\. It is $" + total)
else:
    print("You're short, so choose the $250.")

让我们看看一些测试案例是什么样子的。

首先,让我们使用69英寸的身高:

图 2.2 – 情况 1:身高产生 250 美元

图 2.2 – 情况 1:身高产生 250 美元

因此,任何身高为 5'9"的人无论做出哪种选择都不会错,因为他们最终都会赢得 250 美元。

现在,让我们看看我的身高,70英寸:

图 2.3 – 情况 2:身高产生的金额超过 250 美元

图 2.3 – 情况 2:身高产生的金额超过 250 美元

最后,让我们看一下小于 69 英寸的身高,所以让我们使用55英寸:

图 2.4 – 情况 3:身高产生的金额少于 250 美元

图 2.4 – 情况 3:身高产生的金额少于 250 美元

请注意,我们可以调整代码以表达我们想要的内容。我选择使用完整的句子来阐明结果,但您可以根据需要调整代码以满足您的需求或偏好。此时,一些代码可能具有挑战性,但我们将在第八章中深入学习 Python 编程语言,Python 简介

正如您所看到的,我们有三种算法,它们为我们提供了相同类型的信息。其中一种比其他两种更加健壮,但我们的算法是复杂还是简单取决于我们从中需要什么。如果您稍后再次举行这次抽奖,您可能会忘记算法是什么,您是如何编写的,或者一切意味着什么。然而,通过最后的代码,您只需运行它就可以获得大量信息,这比前两种更有帮助。您还可以将所有这些信息作为注释添加到代码中,但我们将在第八章中讨论这些内容,Python 简介

还要记住,我们运行这个程序就好像我们是参赛者一样。虽然这很有帮助,但您可能要考虑如果您是广播电台会做出什么改变。您可以编写一些代码来保存所有运行的实例,以便随后检查和添加所有的奖金。您甚至可以通过代码计算出总和。由于该代码稍微复杂,我们将在整本书中更详细地讨论它,特别是在第八章中,Python 简介

现在,让我们看一些更多的问题和相应的算法,以便更加熟悉计算思维过程。

其他问题

在本节中,我们将继续研究其他问题。对于问题 2,我们将直接进入算法,因为我们在本章的早些时候已经完成了其他步骤。下一个问题也将包括整个计算思维过程。

问题 2 - 儿童足球派对

在本章的早些时候,我们正在为一个足球队筹办派对,每个孩子的费用是 12 美元。我们说孩子的数量是未知的,所以我们将使用变量k来表示未知数量。我们还说我们有一个数学算法,T = 12k,它给出了k个孩子的总费用 T。让我们在这里添加一个条件。如果我们的预算是 200 美元,我们想知道我们是超支、不足还是正好符合预算。

我们也可以使用 Python 为这种情况编写算法:

k = int(input("How many children are coming to the party? "))
T = 12 * k
if T == 200:
    print("You are right on budget, at " + str(T))
elif T <= 200:
    print("You are under budget, at " + str(T))
else:
    print("You are over budget, at " + str(T))

让我们测试一些情况,以验证我们的代码是否有效:

图 2.5–案例 1:有 12 个孩子参加派对

图 2.5–案例 1:有 12 个孩子参加派对

太好了!如果有 12 个孩子参加,我们就不会超支。那么如果有 20 个孩子参加呢?让我们看一下:

图 2.6–案例 2:有 20 个孩子参加派对

图 2.6–案例 2:有 20 个孩子参加派对

我们没有足够的钱供 20 个孩子!

正如您所看到的,程序为我们提供了有关总数以及我们是否超支或不足的一些信息。与任何算法一样,这并不是我们获取此信息的唯一方式。尝试使用不同的算法来解决这个简单的问题,或者添加一些自己的条件并编写代码。练习和添加条件将使您更加熟悉设计和编写算法。

问题 3 - 储蓄和利息

现在我们有一个新问题。一家银行以每月x%的复利率支付复利。如果您存入任意金额,经过多年后将会支付多少?

让我们分解一下这个问题。首先,我们知道利息是按月复利的。让我们谈谈复利。投资的利息是在一段时间内支付的百分比。复利意味着利息每次都是在初始金额加上利息支付的。复利是一种模式。实际上,它有一个公式。

我不知道的是银行支付的百分比、存款金额或存款年限。因此,我们需要编写一个程序来处理所有可能性。这就是模式概括。我们知道的是利息是按月复利的。实际上,这有一个数学公式:

让我们谈一下上述方程中的术语:

  • A是总金额。

  • P是本金,即初始存款。

  • r是利率(请记住,对于 3%,利率写作 0.03,例如)。

  • n是每年复利的次数。

  • t是存款未动的年数。

因为有一个数学算法,我们现在可以使用公式为此创建一个程序。但是,我们需要确保运行程序的人知道我们要求关于所有输入的内容。我们需要询问一些事情:

  • 存入多少金额?

  • 银行支付的利率是多少?

  • 存款多少年?

我们知道公式中的n。这里n等于 12,因为这是按月复利。这意味着每年会复利 12 次。所以,n = 12。

现在是时候编写这个程序了:

图 2.7 - 复利样本程序

图 2.7 - 复利样本程序

上面的截图显示了复利的 Python 程序。请注意注释前面有#符号。它说明我们需要将利率转换为公式中使用的形式。否则,我们将得到一个不正确的总额。此外,我们在这里使用了浮点数,因为我们需要使用小数。整数或int不会给我们所需的信息。此外,我们将总额四舍五入到两位小数。这是因为我们在谈论金钱时使用两位小数。图 2.7中显示的算法文本如下:

P = float(input("How much are you planning on depositing? "))
r = float(input("At what monthly compound rate will it be paid out? "))
t = float(input("How many years will the money be deposited? "))
#Convert the rate to a decimal for the formula by dividing by 100
r = r/100
A = P * (1 + r/12)**(12*t)
A = round(A, 2)
print("Total after " + str(t) + " years: ")
print(A)

使用复利算法中的代码,如果我们有初始金额、利率和存款年限,我们可以运行任何可能的复利实例。给定初始存款$1,000,利率 4.5%,存款 10 年的程序输出如下:

图 2.8 - 复利输出示例

图 2.8 - 复利输出示例

正如您所看到的,按月复利存入$1,000,10 年后的总额为$1,566.99。

让我们再次测试程序。这一次,我们将以 3.5%的利率按月复利存入5,000美元,存款期为20年:

图 2.9 - 复利输出示例

图 2.9 - 复利输出示例

20 年后我们的总额将是$10,058.51。这意味着我们的钱翻了一番!

有了这个计算器程序,我们只能计算按月复利的利息。我们可以创建一个新程序来计算以任何利率复利的利息:每月、每年、每两个月等等。您可以尝试使用代码来创建自己的计算器。如果您想知道投资或存款在未来会有什么预期,这些都是很有帮助的。例如,您可以确定需要多长时间才能将存款达到一定金额。比如,您想要为孩子的大学教育存入 50,000 美元。在这种情况下,您可以计算出在 18 年内需要存入多少金额,到那时他们很可能已经准备好上大学了。

总结

在这一章中,我们已经经历了计算思维的过程。我们了解了计算思维的四个主要元素:分解、模式识别、模式概括和算法设计。我们还了解到,在计算思维中的问题并不总是清晰定义的。我们需要解释和分解信息,以便找到模式。一旦我们找到模式并定义了我们想要概括的内容,我们就可以设计算法来解决问题。

我们还了解到,算法和解决方案并不是唯一的。对于我们遇到的每个问题,都有多种编写算法的方式。计算思维过程使我们能够以多种方式探索问题,并创建符合我们自己解释和需求的解决方案。

在下一章中,我们将更深入地学习算法和算法思维,因为它们是所有计算思维问题的产物。为了更好地理解算法,我们将研究布尔运算符,并学习如何识别和解决逻辑处理中的错误。我们将为所提出的问题编写算法,并分析给定算法中可能存在的处理错误。

第三章:理解算法和算法思维

在本章中,我们将更加专注于理解算法和算法思维。虽然这是计算思维过程中的最后一步,但我们必须理解算法思维如何帮助我们更好地规划和理解问题。也就是说,我们练习算法设计和算法思维的次数越多,就越容易理解、分解和识别问题呈现给我们时的模式。

在本章中,我们将涵盖以下主题:

  • 深入定义算法

  • 设计算法

  • 分析算法

阅读完本章后,你会更好地理解算法。因此,我们将重新分析算法的定义,这是我们之前在第二章中涵盖的,计算思维的要素,以及如何设计数学和计算算法。

技术要求

你需要最新版本的 Python 来运行本章中的代码。你可以在这里找到本章使用的完整源代码:github.com/PacktPublishing/Applied-Computational-Thinking-with-Python/tree/master/Chapter03

深入定义算法

正如我们在第二章中提到的,计算思维的要素算法只是一组指令。我们在日常生活中使用指令,有时是有意识的,有时是无意识的。例如,想想你早上遵循的例行程序。闹钟响了。接下来你会做什么? 你会准备咖啡吗? 洗澡? 先刷牙吗?

大多数人每天早上都会按照相同的步骤。你可以说我们已经编程自己遵循这些步骤。现在想想一个你的日程变化,你的例行程序不同了的时候。我知道我不得不停下来重新调整很多次,因为我的“程序”不再起作用了。例如,我不能在早上 6 点起床赶 5 点的飞机。

计算机的算法类似,如果一组条件发生了变化,我们需要重新编写一组指令。程序只能执行我们已经规定的参数。大多数程序无法调整或适应任何未事先编码的新信息。也就是说,机器学习人工学习正在发展。我们不是在谈论那些类型的程序,但即使在这些情况下,我们仍然需要调整这些程序以满足我们的需求。

为了设计算法,我们需要确保它们满足一些特定的特征:

  • 它们是清晰和明确的。

  • 它们有明确定义的输入。

  • 它们有明确定义的输出。

  • 它们是有限的。

  • 它们是可行的。

  • 它们与语言无关。

让我们逐个查看前面列表中的每个特征并对其进行定义。

算法应该是清晰和明确的

当每个步骤都能轻松理解、容易定义,并且输入和输出也清晰明确时,算法就是清晰和明确的。算法的每个组成部分应该只有一个意思。

算法应该有明确定义的输入和输出

算法的输入可以是用户提供的,这意味着程序的用户输入数据。输入也可以指程序内部定义的内容。这意味着我可以包含一个已经提供了固定值的变量。

例如,如果我需要用户告诉我他们购买的票数,我可以编写算法来询问输入。我也可以将该输入作为一个已定义的变量,并赋予一个给定的值。算法并不总是需要输入 - 零输入算法是存在的 - 但是当算法需要输入时,定义该输入是很重要的。一个输入的例子是在程序中要求用户的姓名。想想现代视频游戏。其中许多游戏会提示用户输入姓名,比如,“你好,旅行者。你叫什么名字?”

作为用户,当给出提示时,我会输入Sofia,这给了我以下结果:

"Hello Sofia. Welcome to the adventure!"

正如你所看到的,游戏将产生一个输出,并在该输出中使用我的名字。

这最后一行是程序的输出。我也可以用 Python 编写一个简单的程序来问这个问题:

ch3_nameprompt.py

name = input("Hello traveler. What is your name? ")
salutation = "Hello %s. Welcome to the adventure!" % name
print(salutation)

请注意,我们在这里使用了%s%符号。这里的语法是我们所谓的 f-string。我们使用%s语法来让程序知道我们想要插入信息的位置,然后我们通过使用%符号来调用该信息。在这种情况下,我们将输入保存到name变量中,然后在salutation变量中调用它。

运行时,程序看起来是这样的:

Hello traveler. What is your name? Sofia
Hello Sofia. Welcome to the adventure!

这个简单的算法允许我们将名字保存为一个变量。该变量仅在这个简单代码的输出中使用了一次。然而,在游戏中,该名字变量可能会在多个实例中使用,比如在与游戏中的角色对话时等。

程序的输出是离开系统的信息,也就是你的程序的产物。根据一些信息或代码,输出是从程序中的指令产生的东西。

算法应该具有有限性

算法必须具有有限性。这意味着算法必须结束。让我们看一个算法不会结束的情况。我不建议编写或运行这个!尽管如此,让我们看看创建这个算法的步骤:

  1. 定义一个变量i,并将其设置为等于0
i = 0
  1. 增加值1。我们可以以几种不同的方式来做到这一点:
i = i + 1
i += 1 

前面两行代码都会将i的值增加1

  1. 添加一个错误!我们即将在有限性中创建一个错误。再次强调,我只是为了证明一个观点,但这是一个你要避免的错误:
i = 0
while i >= 0:
    i += 1
    print(i)

在这个算法中,我告诉程序继续将i增加1,只要它大于0,然后计算机应该打印这个值。这将永远继续下去,永远不会停止,因为条件将始终成立。因此,程序的输出将从 1 开始,但将继续打印序列中的下一个项目,如 2、3、4、5 等。程序根本没有结束的方法。

现在,可以根据几种不同的条件来完成类似的程序。假设我们想打印所有加法的值,但只要i小于 15:

ch3_finiteness.py

i = 0
while i < 15:
    i += 1
    print(i)

前面的程序是一个终止程序。现在它只适用于i小于15的所有值(不包括 15)。我们将得到如下所示的输出:

图 3.1 - 有限性代码的输出

图 3.1 - 有限性代码的输出

我知道我说过这个程序不包括 15。确实不包括。因为这发生在i小于 15 的情况下,它将评估的最后一个值是 14。然而,它说当值小于 15 时,我们将其增加 1(i += 1)。所以,当i为 14 时,打印的值是 14 + 1,即 15。有限性允许程序终止。

算法必须是可行的

算法还必须是可行的。为了可行,算法需要使用可用的内容和资源。在编写算法时,我们有约束或条件可以写入步骤中。如果没有办法满足所有约束,那么算法就不可行。考虑以下给出的两个条件:

  • 现在是下午 3 点。

  • 现在是下午 5 点。

如果我们对一个变量设置这两个约束,比如,它不可能。它不能同时是下午 3 点和下午 5 点。这就是我们所说的不可行。虽然算法可以继续,但我们通过同时使这两个事情成为真实的问题。有些约束永远不会被满足,因此算法被认为是不可行的。算法必须有一种方法来满足所有约束才能被认为是可行的。此外,如果算法是依赖未来技术编写的,也被认为是不可行的。

算法是与语言无关的

最后,算法必须是与语言无关的。算法中的一组指令应该尽可能简单地编写。一个好的算法将是这样的,它可以很容易地用任何语言编写,并产生相同的输出。

在这一部分,我们学习了算法以及设计算法所需的特征。牢记一个好算法的特征将使我们能够避免错误,并为我们所面临的任何问题创建可行的算法,现在让我们看看如何设计一些算法。

设计算法

设计算法时,顺序很重要。在使用编程语言时,存在重要的层次结构。这包括在使用 Python 时。把这看作是数学中的运算顺序。如果你还记得,我们使用 PEMDAS 来记住数学中的运算顺序。PEMDAS 代表括号、指数、乘法/除法和加法/减法。

我把乘法/除法写在一起是因为乘法和除法具有相同的权重。也就是说,乘法不一定需要在除法之前发生。如果我先进行除法,然后从左到右进行乘法,那么除法先发生。加法和减法也是如此。两者都没有更多的权重,所以我们按照从左到右的出现顺序执行它们。

让我们为一个问题编写一个数学算法。我们将看一个食物环境中的算法。是的,我知道我经常写关于食物和食物算法。我几乎和我喜欢代码一样喜欢食物。

问题 1 - 办公室午餐

办公室正在为员工订餐。员工可以选择三明治或沙拉两种午餐选项。每份三明治餐的成本为 8.50 美元,而每份沙拉餐的成本为 7.95 美元。

办公室午餐数学算法

选择每个选项的员工数量是未知的。让我们使用一些变量来帮助我们设计数学算法。让我们用 s 表示三明治的数量,用 b 表示沙拉碗的数量。我知道你在想什么,如果你过一段时间再回到这个问题,这两个变量并不是很有用。但我们马上就会谈到这个问题。现在,让我们写出我们的总成本 c 会是什么样子:

这是一个简单的数学问题,需要两个未知的变量输入 s 和 b,以获得我们的总成本 c。现在让我们看看同样的午餐场景的另一个版本。

办公室午餐 Python 算法

现在让我们在编写程序时考虑一些更多的考虑因素。在为这个问题设计 Python 算法时,我们需要考虑两个角度:程序员和用户。

有时我们既是程序员/开发人员,也是程序的最终用户,但很多时候,我们会为其他人编写或开发内容。重要的是我们要记住这些考虑因素,因为这可能会影响我们编写程序和定义变量的方式。此外,如果我们正在为公司编写程序,其他人可能需要在某个时候去编辑我们的程序。

这意味着我们需要以其他人能够理解的方式编写程序。我们的变量应该易于理解,因此使用简单的单个字母变量可能会使另一个程序员或用户更难理解。让我们看看问题 1的程序。请记住,在这个问题中,我们试图确定给员工提供两种可能选择的办公午餐的最终成本:

  • $8.50 三明治套餐

  • $7.95 沙拉套餐

让我们使用 Python 为这个问题创建程序。首先让我们澄清一些变量。我们将使用完整的单词或用_分隔的一系列单词来定义这些变量。在开始之前,您可能需要回想一下,对于Python 变量,需要遵循一些规则,以免引起错误:

  • 变量必须以字母或下划线(_)开头。

  • 变量只能包含字母,数字和下划线。

  • 变量不能以数字开头。

  • 变量是区分大小写的(alpha不是与AlphaALPHA相同的变量)。

对于问题 1,我们需要三个变量:

  • 午餐的总成本

  • 三明治午餐的数量

  • 沙拉午餐的数量

现在我们需要给它们命名:

  • total_cost = 所有午餐的总成本

  • number_of_sandwiches = 订购的三明治套餐总数

  • number_of_salads = 订购的沙拉午餐的总数

这里重要的是这些变量易于阅读和理解。我应该注意到,我在编程时偏爱小写变量。我确实有一些例外情况,我喜欢使用大写字母,但您会看到许多只有小写字母和下划线的示例。很久以前,我发现即使在编写程序时大写字母对我来说是有意义的,但后来我会忘记哪些字母是大写的,这只是一个额外的头痛,如果我只是使用小写字母,就可以避免。

此外,一些程序员会消除下划线,并使用变量,例如numberofsandwiches或简单的sandwiches。当然,这两种都是可以接受的,而简单的sandwiches会使编写一些代码变得更容易。然而,这样做有利有弊。如果其他人正在查看程序,可读性将很重要。就像我说的,我偏爱清晰的小写变量和使用下划线,但每个程序员都可以自行选择。

现在我已经定义了我的变量,我可以开始编写我的程序了。我需要向用户询问什么? 我需要用户的输入,包括三明治和沙拉的数量。我想要作为输出的是午餐的总成本。在 Python 中向用户询问输入,我们需要使用input命令。然而,我们还需要记住,由于我们在使用float数字的算法中使用了这个数字(小数点是浮点字符),我们需要将提供的数字转换为integerfloat。员工不会点半份沙拉,所以我们可以安全地将它们保存为整数,或int。作为提醒,在 Python 中,注释以#符号开头。在 IDLE 中编写代码,如下所示:

ch3_officelunch.py

#Ask the user for the number of sandwich meals ordered and save as variable.
number_of_sandwiches = int(input("How many sandwich lunches were ordered? "))
#Ask the user for the number of salad meals ordered and save as variable.
number_of_salads = int(input("How many salad lunches were ordered? "))
#Create total_cost variable and save the algorithm for total the new variables.
total_cost = 8.50 * number_of_sandwiches + 7.95 * number_of_salads
#Print the total cost. Don't forget to convert the total_cost to string. 
print("The total cost for the employee lunch is $" + str(total_cost) + ".")

运行代码时,用户可以输入办公午餐的各种选项的数量。代码首先询问用户三明治的数量,如下所示:

How many sandwich lunches were ordered? 

然后代码将要求输入沙拉午餐的数量,并提供总费用。以下示例输入 12 份三明治午餐和 23 份沙拉午餐,总费用为 284.85 美元:

How many sandwich lunches were ordered? 12
How many salad lunches were ordered? 23
The total cost for the employee lunch is $284.85.

现在让我们从不同的角度来看一个类似的问题。

问题 2 - 一个餐饮公司

假设你开始了一个简单的餐饮公司。你开始只销售两种选择,一份 8.50 美元的三明治套餐和一份 7.95 美元的沙拉套餐。你可以创建一个使用 Python 字典存储这些选项的程序。

你可以在第八章中找到有关 Python 编程语言和字典的更多信息,Python 简介,但我们也会在这里定义一个 Python 字典。当我们想要无序、可更改且有索引的项目时,我们使用字典。以下是 Python 中我们餐饮公司的一个字典的示例:

ch3_cateringdict.py

catering_menu = {
    "sandwiches": 8.50,
    "salads": 7.95
    }
print(catering_menu)

现在,字典是常见的,而且非常有用,原因有很多:主要是它们易于阅读,并且它们提供了根据需要更改数据的方法。

当打印时,字典代码看起来像这样:

{'salads': 7.95, 'sandwiches': 8.5}

现在你有了一个字典,让我们谈谈它对你的餐饮公司的用处。假设你的沙拉配料成本增加了,你想通过改变沙拉的价格来进行核算。你可以通过几种不同的方式来做到这一点。你可以在原始程序中进行更改,因为它非常简短,或者你可以告诉程序你想要根据键值来进行更改。这很重要,因为现在你可能有两种出售的物品,但是当你的菜单选项变得更加广泛时会发生什么? 每次更改价格时,你是否想要搜索每个项目? Python 使得识别你想要更改的内容然后进行更改变得很容易。

为此,你可以使用以下代码:

catering_menu["salads"] = 9.50

你的新 Python 代码看起来像这样:

ch3_cateringdict2.py

catering_menu = {
    "sandwiches": 8.50,
    "salads": 7.95
    }
catering_menu["salads"] = 9.50
print(catering_menu)

当打印时,沙拉的新价值将被显示:

{'salads': 9.5, 'sandwiches': 8.5}

但是,如果你想添加一个菜单项目会发生什么? 比如说你想添加一个售价为 3.75 美元的汤选项。在这种情况下,你可以通过一行简单的代码将菜单选项添加到你的字典中,如下所示:

catering_menu["soup"] = 3.75

当你把所有东西放在一起时,初始代码和更改将看起来像下面的代码块。请注意,你有初始字典,然后是下面的两个更改。当你打印字典时,它将包括所有更改以及添加汤选项:

ch3_cateringdict3.py

catering_menu = {
    "sandwiches": 8.50,
    "salads": 7.95
    }
catering_menu["salads"] = 9.50
catering_menu["soup"] = 3.75
print(catering_menu)

现在你已经添加了soup项目,你可以打印你的字典来查看你的完整菜单:

{'soup': 3.75, 'salads': 9.5, 'sandwiches': 8.5}

我们可以使用字典中的信息来创建更健壮的程序,比如在线菜单、点餐菜单选项等等。在本节中,我们学习了如何设计算法,并解决了两个问题。

我们将在本书的后续章节中更多地使用 Python 进行开发,特别是在第三部分使用计算思维和 Python 进行数据处理、分析和应用。现在,让我们继续分析一些算法。

分析算法

正如本章前面提到的,当我们设计算法时,它们应该具备以下特点:

  • 它们是清晰和明确的。

  • 它们有明确定义的输入。

  • 它们有明确定义的输出。

  • 它们具有有限性。

  • 它们是可行的。

  • 它们是与语言无关的。

除了这些特点之外,当我们看算法并分析它们时,我们希望确保自己问一些问题:

  • 算法是否做我们想要的?

  • 输出是否合理?

  • 是否有其他更清晰地获取相同信息的方法?

在分析算法时,我们可以问自己许多更多的问题,但现在让我们看一些算法解决方案,并根据上述特点和问题进行分析。

算法分析 1 - 州和首府

一个学生创建了一个算法,其中包括美国各州和每个州的首都的列表,但只包括她已经学过的州。她的算法如下所示:

ch3_statecapitals1.py

Ohio = "Columbus"
Alabama = "Montgomery"
Arkansas = "Little Rock"
print(Ohio)

这个程序很简单,但使用起来并不容易,也不在运行时提供帮助。它包含所需的信息吗?是的。我们可以以不同的方式组织它,以便以其他方式调用信息吗?可以。

把州和首都看作键对。我们可以使用字典来存储这些信息。你可能还记得本章前面提到过,字典可以很容易地进行调整和适应,只需一行简单的代码就可以添加一个新的键。让我们先把前面代码中的信息转换成字典:

ch3_statecapitals2.py

state_capitals = {
    "Ohio" : "Columbus",
    "Alabama" : "Montgomery",
    "Arkansas" : "Little Rock"
    }
print(state_capitals["Ohio"])

注意,现在我们可以通过简单地给出州名来访问州首都的信息。这段代码的输出只是哥伦布但是如果你只想运行程序并要求用户输入他们选择的州呢?我们也可以用现有的字典在一行代码中编写。看一下以下代码:

ch3_statecapitals3.py

state_capitals = {
    "Ohio" : "Columbus",
    "Alabama" : "Montgomery",
    "Arkansas" : "Little Rock"
    }
state = input("What state's capital are you looking for today? ")
capital = state_capitals[state]
print("The capital of " + state + " is " + capital + ".")

在这段代码中,用户输入他们想要查找首都的州。这很有帮助,因为你可以每次运行代码而不必进入代码中去更改要打印的代码行,这是我们在ch3_statecapitals2.py文件中的算法中不得不做的。运行时,代码看起来像这样:

What state's capital are you looking for today? Alabama
The capital of Alabama is Montgomery.

现在让我们看一下首先需要这个算法的原因。学生想要继续向程序中添加州。由于这个程序是基于字典的,她只需在需要添加另一个州时添加一行代码。例如,如果她想要添加爱荷华州,其首都是得梅因,她需要使用以下代码:

state_capitals["Iowa"] = "Des Moines"

看一下以下代码块。注意代码在程序中的放置位置。很重要的是我们把新代码放在新变量之前,否则,如果你尝试运行程序并输入爱荷华州,代码将返回一个错误而不是提供爱荷华州的首都。

在算法中,逻辑非常重要。我们不能在已经使用过的变量中使用我们尚未定义的值。也就是说,如果在为爱荷华州标识新值之前就使用了首都变量,那么当输入爱荷华州时,代码会以错误结束。然而,如果我们在运行这两个变量之前添加键对值,代码就会如预期般运行:

ch3_statecapitals4.py

state_capitals = {
    "Ohio" : "Columbus",
    "Alabama" : "Montgomery",
    "Arkansas" : "Little Rock"
    }
state_capitals["Iowa"] = "Des Moines"
state = input("What state's capital are you looking for today? ")
capital = state_capitals[state]
print("The capital of " + state + " is " + capital + ".")

正如你所看到的,我们可以调整和修改代码以更好地满足我们的需求。现在让我们看一下一些算法,确定它们是否会运行;也就是说,它们是否会产生错误或正确运行。

算法分析 2 - 终止还是不终止?

正如我们在本章前面讨论过的那样,算法应该是终止的。也就是说,它们必须有一种结束的方式,否则会导致许多错误。让我们看一个算法并分析它,确定它是否会终止:

x = 0
while x >= 3:
x += 1
print(x)

首先,让我们看一下x变量的值。x变量以0的值开始程序。while循环规定了x值将发生变化的条件,当x值大于3时,它会增加1的值。

这个算法终止,因为它将打印变量0的原始值。然而,这个算法实际上并不执行任何操作,因为条件永远不会满足。另外,请注意print命令没有缩进。如果缩进了,对于这个算法将不会给出任何输出,因为print命令永远不会被调用,因为变量永远不会满足while循环的条件。

现在让我们看一下以下算法:

j = 0
while j >= 0:
	j -= 1
	print(j)

在这种情况下,变量条件得到满足,因为j必须大于或等于 0 才能使程序运行。一旦条件得到满足,变量的值就会减少 1,因此print命令将产生一个输出为-1。代码不会再次运行,因为变量的值不再大于或等于 0。这个算法是终止的,产生了输出,并且是可行的。

最后,让我们来看一下以下具有改变条件的算法:

j = 0
while j <= 0:
	j -= 1
	print(j)

在这种情况下,算法是不终止的。因为我们将while循环改为小于或等于 0,这个算法现在将永远继续运行。

分析算法可能非常复杂。我们只是开始涉及算法的一些组成部分。随着我们在本书中深入研究其他计算思维问题,我们需要牢记良好算法的特性,以便有效地分析我们自己的代码。同样重要的是,我们继续考虑计算思维过程的元素:分解模式识别模式概括算法设计

当我们设计算法并测试它时,使用良好算法的特性将使我们能够观察错误,调整我们的算法以便使用,提供更好的输入和输出,并确保我们不会创建不可行和非终止的算法。

总结

在本章中,我们讨论了算法的定义,即一组步骤,允许计算机完成一个过程并提供一些输出。我们了解了算法的特性。

我们设计了基于问题场景的算法,然后分析算法,以确定它们是否满足正确运行所需的特性。理解算法的特性以及算法的工作原理将使我们能够创建比不了解这些特性时错误要少得多的算法。请注意,我说的是更少的错误。

在编写代码时,错误是生活中不可避免的事实。我们将不可避免地犯错误,我们将无意中引入错误或使一些代码无限。了解良好算法的特性使我们能够减少这些错误,即使我们无法完全将它们从我们的日常生活中消除。

在下一章中,我们将学习更多关于逻辑推理的知识。在整个章节中,我们将讨论逻辑的定义,学习归纳和演绎推理,增加我们对运算符和布尔逻辑的了解,并更多地了解逻辑错误。我们将使用计算思维的元素和算法的特性来进一步增进我们对逻辑推理的了解。

第四章:理解逻辑推理

在本章中,我们将探讨条件语句、算法推理和布尔逻辑等逻辑推理过程。在深入研究一些逻辑运算符之前,我们将探讨归纳和演绎推理。我们还将学习逻辑错误,如何识别它们以及如何纠正它们。

此外,我们将研究使用逻辑来编写算法以解决计算思维问题。

在本章中,我们将涵盖以下主题:

  • 理解逻辑推理的重要性

  • 使用布尔逻辑和运算符

  • 识别逻辑错误

在解决计算思维问题时,逻辑推理是必要的。我们都知道编程代码有按顺序执行的步骤。想象一下我们有 10 行代码。如果我们不应用逻辑推理,代码会一行一行地读取——读取第一行,读取第二行,依此类推,直到最后一行。使用逻辑推理允许我们在继续之前比较事物,返回到以前的代码行,等等。在本章中,我们将学习逻辑推理,以便使用逻辑运算符以高效的方式解决问题并创建算法。

为了理解逻辑推理,我们将首先概括逻辑的一般定义,然后讨论在设计和编写算法时如何使用逻辑。

技术要求

您需要最新版本的 Python 来运行本章中的代码。您可以在此处找到本章使用的完整源代码:github.com/PacktPublishing/Applied-Computational-Thinking-with-Python/tree/master/Chapter04

理解逻辑推理的重要性

当我们通过第三章 理解算法和算法思维时,我们学会了如何在解决计算思维问题时设计一些基本算法。

在本节中,我们将了解逻辑推理的重要性以及如何通过示例应用逻辑推理的类型。

在编写算法时,我们需要使用逻辑推理来创建这些算法。

简而言之,逻辑推理是达成结论所遵循的一系列步骤。在计算思维中,当我们设计算法时,我们遵循的系统步骤是算法的一部分。计算机读取这些算法的方式取决于我们编写算法的方式。逻辑推理论证有两种类型,如下所示:

  • 归纳推理

  • 演绎推理

在更深入地定义这些之前,让我们看看为什么逻辑推理如此重要,以及在创建算法时为什么顺序很重要。

为了分析问题并提供帮助我们解决问题的算法,我们首先需要了解什么是逻辑推理。对于一些人来说,逻辑可能令人望而生畏,但我们每天都在下意识地使用它。

让我们看一个简单的例子。假设您每天早上洗澡然后去上班。那么,您会在洗澡之前穿上工作服吗?不会,因为那完全没有意义。从逻辑上讲,您必须先洗澡,然后再穿上工作服。现在,我在这里跳过了很多步骤,但这些步骤是逻辑步骤。其他逻辑的例子包括按照食谱制作食物,下雨时使用伞(或不使用),等等。

在本章中,我们将交替讨论逻辑推理和使用逻辑运算符设计算法。逻辑运算符允许程序做出决策。我们在日常生活中也会使用它们,只是没有意识到而已。例如,如果天气晴朗而温暖,我们可能想去骑自行车,但如果天气晴朗但寒冷就不想去。这里的and是一个逻辑运算符。

在做决定时,我们考虑了很多因素。在计算思维中,特别是在算法设计中,我们需要考虑这些因素,并为程序提供一种测试这些条件的方法。我们将在本章后面更深入地探讨逻辑运算符。现在,让我们更仔细地看看逻辑推理的类型以及如何应用它们。

应用归纳推理

当我们谈论归纳推理时,我们实际上是在向后工作。归纳推理从一个可能为真或假的结论开始,然后向后使用现有数据创建代码。让我们先看一个简单的问题。

解决归纳推理样本问题

我们有 150 美元的预算用于购买美术用铅笔和橡皮擦。美术用铅笔每支 1.75 美元,橡皮擦每个 1.50 美元。

记住,在计算思维中,我们首先分解问题,然后识别模式,然后概括该模式,然后创建算法。因此,让我们认识到这种模式。

让我们看看到目前为止我们知道的内容并命名一些变量:

  • 总预算是 150 美元。

  • 铅笔的成本是每支 1.75 美元。

  • 橡皮擦的成本是每个 1.50 美元。

  • 现在让我们用p表示铅笔的数量。

  • 让我们用n表示橡皮擦的数量。

请记住,当我们到达算法时,我们可能想要重新命名这些变量。但现在,因为我们首先要看数学算法,所以我们会保留简单的变量。

我们可以用一个不等式来表示。为什么是不等式而不是方程?因为我们的总额可能不会正好是 150 美元。但它不能超过 150 美元,因为那是我们所有的钱。

因为这是一个简单的问题,我们一举识别和概括了这个模式。

因此,铅笔的数量乘以成本加上橡皮擦的数量乘以成本小于或等于 150 美元:

现在让我们谈谈算法设计。也许这是我定期购买的东西,因为我开美术课。我会根据这种情况进行。也许我的雇主最多给我 150 美元,但根据以前使用的情况,我可能需要更多的铅笔或橡皮擦,反之亦然。因此,我需要一个可以在每学期开始时使用和重复使用的程序。这是我的问题的一部分吗?不,这是一个定义不清晰的问题。因此,我正在根据一组特定需求调整问题。简而言之,我正在定义我想要解决的问题。

重要提示:

作为归纳和演绎推理困境的一则旁注,重要的是要理解,条件语句(例如我们在编程中经常使用的if/then语句)通常与演绎推理相关联。我们可以就它们是否可以是归纳的进行辩论,但事实是,归纳推理问题将使用演绎推理。我们将在本章的下一节深入研究演绎推理陈述。

因此,我希望程序问我想要多少支铅笔,或者问我想要多少个橡皮擦。这完全取决于情况!让我们看看程序应该为我们做些什么。以下步骤向我们展示了这一点:

  1. 询问您的输入是铅笔还是橡皮擦。

  2. 根据提供的输入选择要使用的不等式。

  3. 确定可能的铅笔或橡皮擦的数量(根据输入)。

  4. 给出铅笔和橡皮擦的总成本。

请注意,像往常一样,有很多种方法可以在 Python 中得出相同的答案。虽然其中一些程序比我通常呈现的要长,但由于我们正在学习计算思维和 Python 编程,展示易于理解的步骤非常重要。

对于这个特定的程序,我们需要导入数学函数,以便我们可以向下取整。为什么我们需要这样做?嗯,我们不能购买部分橡皮擦和铅笔,只能购买整支铅笔和整个橡皮擦。因此,如果程序说我们可以购买 19.5 支铅笔,那实际上意味着我们只能购买 19 支铅笔。

math.floor() 函数允许我们使用简单的函数将该数字向下舍入到 19。在本书中,我们将更多地探讨 math 函数。在我们离开这个快速主题之前,你应该知道 Python 中的 math 模块具有与 C 语言函数相匹配的内置函数。

让我们回到问题。看一下下面的编程:

ch4_inductiveP1.py

#We need the math module, so don't forget to import it.
import math
#Ask the user if they will input pencils or erasers first.
item1 = input("Will you be entering pencils or erasers? ")
if item1 == "pencils":
    pencils = int(input("How many pencils will you purchase? "))
    if pencils * 1.75 < 150:
        pencilstotal = pencils * 1.75
        total = 150 - pencilstotal
        total = total / 1.50
        erasers = math.floor(total)
        total2 = pencilstotal + erasers * 1.50
        print("You will be able to purchase " + str(pencils) + " pencils and " + str(erasers) + " erasers for a total cost of $" + str(total2) + ".")
    else:
        print("That's too many pencils.")
elif item1 == "erasers":
    erasers = int(input("How many erasers will you purchase? "))
    if erasers * 1.50 < 150:
        eraserstotal = erasers * 1.50
        total = 150 - eraserstotal
        total = total / 1.75
        pencils = math.floor(total)
        total2 = pencils * 1.75 + eraserstotal
        print("You will be able to purchase " + str(pencils) + " pencils and " + str(erasers) + " erasers for a total cost of $" + str(total2) + ".")
#If the input given is too large based on the budget, this line of code alerts the user.
    else:
        print("That's too many erasers.")
#If the input is incorrect, the program will print a statement to alert the person that they need to use pencils and erasers as input first.
else:
    print("Please run the program again and enter erasers or pencils as your input.")

请记住,上述程序将按顺序运行代码行(顺序)。因此,如果用户首先输入 橡皮擦,那么第一个 if 语句和嵌套的 if 语句将被忽略。如果用户首先输入 铅笔,那么算法将从第一个 if 语句正常运行,并通过剩余条件。以下是程序的顺序操作:

  1. 要求用户输入他们是在买铅笔还是橡皮擦。

  2. 如果用户输入铅笔,那么程序会询问他们要购买多少支铅笔。然后,它会计算他们能够购买多少个橡皮擦。

  3. 如果用户输入的铅笔数量太多,他们将收到一条消息,说明他们买不起那么多。

  4. 如果用户输入橡皮擦,那么程序会询问他们要购买多少个橡皮擦,然后计算用户能够购买多少支铅笔。

  5. 如果用户输入的橡皮擦数量太多,他们将收到一条消息,说明他们买不起那么多。

  6. 如果用户既不输入铅笔也不输入橡皮擦,他们将收到一条消息,要求重新运行程序并输入这两个选项中的一个。

上面是一个过于简化的归纳推理问题。一些归纳推理问题会要求你查看数据,得出一些可能的结论,然后编写一个程序来测试这些结论。在学习逻辑推理的过程中,我们基本上是在训练自己如何看待决策以及如何以程序可以返回我们所期望的输出的方式来处理它们。

在这里需要注意的是,有多种方法可以看待问题并准备解决方案。虽然我更喜欢决策树和流程图,其他程序员和开发人员更倾向于更数学化的方法。还有一些人喜欢用简单的句子和/或段落写下程序需要做的事情。这个过程的目的是让我们创建一个能够产生必要输出并且逻辑上易于程序员、开发人员和运行它的计算机理解的程序。

现在,让我们来看看演绎推理。

应用演绎推理

现在我们来到本章重点讨论演绎推理的部分。即使我是一名数学学生,我也发现演绎推理很迷人。我很快就学会了数学如何教导我们如何在几何中逻辑地跟随论点,我爱上了所有逻辑和真值表的事物。

逻辑是通过证明和归纳和演绎推理来教授的。真值表帮助我们分析条件。在真值表中,有一些假设。例如,一个陈述要么是真的,要么是假的。另一个陈述也是真的或假的。这些陈述的组合取决于这些陈述是真还是假。

好的,这有点复杂。在我继续解释演绎推理之前,让我们先看一个快速的真值表和其中包含的逻辑过程。

真值表在我刚开始编码时非常重要。它们帮助我理解编码过程以及如何处理条件。并不是每个程序员或编码人员都使用这些表,但我发现它们很有帮助,即使在决策过程中没有明确使用。现在让我们来看一个。

假设我们有一个陈述或条件p,并且该条件为True。假设我们有另一个陈述或条件q,并且它也为True。在真值表中,我们使用符号¬来表示NOT。所以,¬p¬是False,¬q也是False。这是因为如果pTrue,那么NOTp就是NOTTrue,换句话说,就是False。符号用于AND,所以pANDq写作p ![](https://gitee.com/OpenDocCN/freelearn-python-pt2-zh/raw/master/docs/app-cmp-thk-py/img/AND_symbol1.png) q。符号![](https://gitee.com/OpenDocCN/freelearn-python-pt2-zh/raw/master/docs/app-cmp-thk-py/img/OR_symbol.png)用于OR,所以pORq写作p ![](https://gitee.com/OpenDocCN/freelearn-python-pt2-zh/raw/master/docs/app-cmp-thk-py/img/OR_symbol.png) q。在表格格式中,我们的真值表如下:

图 4.1 - 真值表

图 4.1 - 真值表

分析真值表并理解所有可能的条件可能需要时间,但这个过程类似于我们在编写问题的逻辑推理时经历的过程。现在,让我们更仔细地看看演绎推理。

让我们首先定义什么是演绎推理。演绎推理是从一个陈述或假设到结论的过程。因为演绎推理是我们在算法设计中使用的,所以在很大程度上,我们需要定义一些与之相关的术语。

让我们从条件陈述开始。

学习条件陈述

条件陈述是 if/then 语句。以下是一些使用条件陈述的逻辑论据:

  • 如果下雨,我会用伞。

  • 如果我喝水,那么我就不会渴了。

  • 如果我的狗需要出去,那么它会站在门边。

  • 如果一个四边形有四个直角,那么它是一个矩形。

所有前述陈述都是条件陈述的例子。陈述的第一部分称为假设。陈述的第二部分是结论。在陈述如果下雨,我会用伞中,假设是下雨,结论是用伞。我们在假设和结论中不包括ifthen

在 Python 中,正如您在应用归纳推理部分的示例中看到的,我们在编写算法时经常使用if/then语句。以下是我们在 Python 中使用的一些逻辑陈述:

  • if:在使用if语句时,我们询问条件是否满足,然后根据真假条件执行某些操作。

  • if-else:在使用if-else语句时,我们测试一个条件并执行某些操作,但如果条件不满足,那么我们执行其他操作。

  • if-elif-else:在使用if-elif-else语句时,我们有一个条件;如果条件不满足,我们测试另一个条件,即else ifelif)条件,否则,我们执行其他操作。

所有前述陈述都可以嵌套。我可以测试一个条件,然后另一个条件,然后另一个条件。我们可以在ifelse之间有多个elif语句,依此类推。让我们看一些例子。

if 语句

让我们看一个只使用一个if语句的程序:

ch4_ifStatement.py

number = int(input("What's your favorite number? "))
if number < 100:
    print("That's not a very large number.")

现在,上述代码是一个简单的程序,只检查一个条件。我们可以添加条件来测试number是否等于100。如果number大于100,我们可以添加另一个条件,依此类推。在这种情况下,我们只关心number是否小于100

如果我们输入数字53,我们将得到以下输出:

What's your favorite number? 53
That's not a very large number.

如果我们输入数字100,我们将得不到任何消息,程序将结束:

What's your favorite number? 100

正如您所看到的,程序没有任何要添加的内容。条件没有满足,所以它结束了。这就是为什么if-else语句会派上用场的原因。

if-else 语句

让我们看看以前的算法,并添加一个else语句。以前的程序只检查提供的数字是否小于100。如果我们添加一个else语句,我们可以在屏幕上打印其他所有大于或等于100的数字。看看以下程序:

ch4_if-elseStatement.py

number = int(input("What's your favorite number? "))
if number < 100:
    print("That's not a very large number.")
else:
    print("I guess you like large numbers.")

前面的程序现在无论用户输入什么数字,都会打印出一条消息。让我们再次测试100

What's your favorite number? 100
I guess you like large numbers.

如您所见,100包括在大数字类别中,因为我们的条件是数字小于100。这意味着100不包括在条件中。测试条件是我们在 Python 中得出结论的方式。我们编写算法从程序本身或用户输入中收集信息,然后测试条件以做出决定。

以下图表显示了if-else决策的流程图。在查看if-elif-else语句和嵌套语句时,我们将查看其他流程图:

图 4.2 - if-else 语句决策流程图

图 4.2 - if-else 语句决策流程图

如前图所示,这是一个二进制决策。语句将被测试以检查它是True还是False。如果True,则会发生一个动作;否则,会发生另一个动作。对于我们的数字程序,如果数字小于100,则打印一条消息;否则,在屏幕上打印另一条消息“我猜你喜欢大数字”。现在,让我们添加多个条件。

if-elif-else 语句

if-elif-else语句是多条件语句的简化形式 - 也就是说,您可以有多个elif语句。如前所述,elif代表else if。让我们稍微改变一下我们的程序。我们将允许用户输入一个介于120之间的数字。这是我们将编程算法的方式:

  1. 要求输入一个介于120之间的数字。

  2. 测试数字是否在110之间,并打印消息。

  3. 测试数字是否在1120之间,并打印消息。

  4. 打印错误消息。

让我们看看我们如何编写这个程序。在编写此算法之前,我们需要记住一些事情。要轻松检查介于110之间的数字,我们需要检查数字是否小于10。这意味着10不包括在内。

我们的elif语句将检查小于21的数字,因为它只包括我们尚未测试过的数字。也就是说,如果用户输入12,第一个条件不满足,所以它会转到第二个条件。是的,这将包括所有小于21的数字,但请记住,如果数字小于10,它将已经满足了一个条件,并且程序将打印正确的消息。

最后,如果条件不满足,我们需要让用户知道他们写了一个不在120之间的数字。以下程序演示了这一点:

ch4_if-elif-elseStatement.py

number = int(input("Pick a number between 1 and 20\. "))
if number < 10:
    print("That's less than 10.")
elif number < 21:
    print("That's between 10 and 20.")
else:
    print("That number isn't between 0 and 20\. Run the program and try again.")

让我们尝试用一个小于10的数字进行测试。如果我们用数字8运行程序,我们会看到以下输出:

Pick a number between 1 and 20\. 8
That's less than 10.

如果我们用数字10运行程序,我们会看到以下输出:

Pick a number between 1 and 20\. 10
That's between 10 and 20.

最后,如果我们用数字21运行程序,我们会看到:

Pick a number between 1 and 20\. 21
That number isn't between 0 and 20\. Run the program and try again.

如您所见,每个条件都为我们提供了该条件的答案。这是if-elif-else语句的流程图:

图 4.3 - if-elif-else 语句决策流程图

图 4.3 - if-elif-else 语句决策流程图

如前图所示,elif只是提出了一个新的测试。如果True,我们就会按照算法的行动。如果False,我们就会转到else语句。也就是说,我们可以有多个elif条件。这意味着我们可以继续一次又一次地测试条件,直到达到else语句为止。

理解嵌套语句

Python 中我们使用的另一种逻辑语句类型涉及嵌套语句。在嵌套条件中,嵌套的if语句只有在前一个if语句为True时才会执行。通过一个例子更容易理解。让我们回到我们的if-elif-else语句,并添加一些嵌套条件。我们之前要求用户输入一个在120之间的数字。现在,让我们使用以下代码进一步细分条件:

ch4_nestedStatements.py

number = int(input("Pick a number between 1 and 20\. "))
if number < 10:
    if number < 6:
        print("Why such a small number?")
    else:
        print("Well, less than 10 but greater than 5\. I'll take it.")
elif number < 21:
    if number < 16:
        print("You like values that are greater than 10, but not too much greater. I guess that's fine.")
    else:
        print("I like larger numbers myself too.")
else:
#Sometimes we make mistakes when providing input in programs. If you choose a number that's not between 0 and 20, the program will print this message.
    print("That number isn't between 0 and 20\. Run the program and try again.")

在上面的代码片段中,代码对于我们输入不符合规定的数字时有一条消息。例如,要求输入的数字在120之间。但是如果用户输入 0 或 21,或者其他不在这个范围内的数字会发生什么?然后,print()语句会提供一条消息,要求用户重新运行程序。

在这种情况下,你可以看到我们有if语句、elif语句、嵌套的ifelse语句等。让我们看一些测试案例,测试一些条件,看看我们的程序会说些什么:

  • 当我们输入4时,我们看到以下输出:
Pick a number between 1 and 20\. 4
Why such a small number?
  • 当我们输入6时,我们看到以下内容:
Pick a number between 1 and 20\. 6
Well, less than 10 but greater than 5\. I'll take it.
  • 当我们输入11时,我们得到如下结果:
Pick a number between 1 and 20\. 11
You like values that are greater than 10, but not too much greater. I guess that's fine.
  • 当我们输入18时,我们得到以下输出:
Pick a number between 1 and 20\. 18
I like larger numbers myself too.

从上面的测试案例中可以看出,我们根据程序中给定的条件有更多的输出。虽然这是一个简单的数字程序,但在解决更复杂的问题时,我们可以使用类似的逻辑。

假设你经营一个在线商店。用户选择的商品将会在类似的算法中使用,尽管这些算法要复杂得多。算法会测试条件,比如选择的商品、选择的数量等,以应用总额、优惠券等。这就是为什么逻辑和逻辑推理在编程中如此重要。

现在,正如之前提到的,我们使用的逻辑处理对于每个程序员可能会有所不同。然而,无论偏好如何,逻辑推理和逻辑处理在我们编写算法时绝对是必不可少的。我们在编写算法时,不是直接开始写,而是处理问题,看决策和需要发生哪些步骤,然后设计算法。这种逻辑过程对于创建有效的算法至关重要。在本书中,我们将继续在分析问题时关注逻辑推理,即使我们没有明确说明。

在本节中,你学习了逻辑推理及其两种类型——归纳推理和演绎推理。我们还学习了在编码时会派上用场的条件语句。

我们编写的一些算法可以使用布尔逻辑和运算符进行简化,这就是我们将在下一节中看到的内容。

使用布尔逻辑和运算符

布尔逻辑指的是 Python 中的andornot等运算符。你可能还记得在本章早些时候对真值表的简要讨论中看到过这些内容。接下来我们将看到,即使没有明确说明或使用这些表,我们在编写算法时仍然使用相同的逻辑处理。在解决计算思维问题时,我们有时需要同时满足多个条件。让我们现在用语言来看一下这个问题。

让我们分类一些水果。如果水果是圆形且橙色、绿色或黄色,它将被分类到组 1。如果水果不是圆形,但是橙色、绿色或黄色,它将被分类到组 2。如果水果不符合这些要求,它将被分类到组 3。让我们简化这些组:

  • 组 1:圆形和(橙色或绿色或黄色)

  • 组 2:非圆形和(橙色或绿色或黄色)

  • 组 3:所有其他水果

我知道我先前提到了圆形条件。但是如果你看一下组 12,水果需要针对这些颜色进行测试,无论是哪种条件——也就是说,如果颜色条件不满足,水果是否圆形并不重要,它都会被放入组 3。所以,这是我会为算法写的内容:

  1. 测试水果是橙色、绿色还是黄色。

  2. 如果是,测试是否圆形,并分类为第 1 组第 2 组

  3. 如果不是,分类为第 3 组

因此,如果我们有一颗柑橘,那就属于第 1 组。如果我们有一根香蕉,它就属于第 2 组。如果我们有草莓,它们就属于第 3 组

现在,如果我们要编写这个,我们需要确保已经添加了水果的特征,以便我们可以对其进行测试。我们将在本书的后续章节中看到类似的内容,但现在,为了简化一些学习,我们将创建一个类似的算法,但使用数字。

在我们继续之前,让我们快速看一下 Python 中的基本运算符:

图 4.4 - 基本的 Python 运算符

图 4.4 - 基本的 Python 运算符

当我们到达第二部分应用 Python 和计算思维时,我们将更深入地了解这些运算符,并深入研究 Python 编程语言。然而,我们需要在下一个算法中使用其中一些。首先,让我们看看and运算符。

and 运算符

为了更好地理解and运算符,最好看一个数学算法。让我们输入一个数字,并测试该数字是否大于100且是2的倍数。要测试一个数字是否是2的倍数,我们使用取模运算符mod)。在 Python 中,mod的符号是%

因此,从代码中可以看出,如果number % 2 == 0,那么这个数字是2的倍数。如果number % 2 == 1,那么它就不是2的倍数。我们使用等于(==)运算符或不等于(!=)运算符来完成这些条件:

ch4_andOperator.py

number = int(input("Give a number between 1 and 200\. "))
if number > 99 and number % 2 == 0:
    print("That's a large, even number.")
elif number > 99 and number % 2 != 0:
    print("That's a large, odd number.")
elif number < 100 and number % 2 == 0:
    print("That's a small, even number.")
else:
    print("That's a small, odd number.")

现在,我知道我们已经讨论了编写算法的不同方法。我需要为这个使用 AND 运算符吗? 可能不需要。我本可以只将其写成嵌套语句,if-elif-else语句等。一些测试用例和算法的结果如下所示:

  • 当我们输入104时,我们看到以下输出:
Give a number between 1 and 200\. 104
That's a large, even number.
  • 当我们输入80时,我们看到以下输出:
Give a number between 1 and 200\. 80
That's a small, even number.
  • 当我们输入31时,我们得到以下输出:
Give a number between 1 and 200\. 31
That's a small, odd number.

从前面的测试用例中可以看出,程序测试了我们的情况,并根据满足的条件提供了打印消息。现在,让我们来看一下or运算符。

或运算符

正如我们在本章前面的水果示例中看到的,我们检查水果的颜色是橙色、绿色还是黄色。这就是or运算符的工作原理。我们检查某种情况或另一种情况。这次,我们将看一些TrueFalse的陈述。假设变量ATrue,变量BFalse。如果我们使用or运算符来检查AB的结果,那么我们的答案将是True

为什么呢?因为无论如何,结果要么是True,要么是False,这是一个True的陈述。困惑吗? 逻辑可能会让人困惑。让我们继续测试以下程序中的A and B以及A or B,以帮助您形象化:

ch4_orOperator.py

A = True
B = False
C = A and B
D = A or B
if C == True:
    print("A and B is True.")
else:
    print("A and B is False.")
if D == True:
    print("A or B is True.")
else:
    print("A or B is False.")

现在,我添加了一些条件,以便我们得到输出,并且您可以看到我所说的逻辑是正确的,但我们不需要做所有这些。我们本可以只打印CD

当我们运行这个程序时,结果如下:

A and B is False.
A or B is True.

正如您所看到的,A and BFalse,因为其中一个陈述是False,这意味着整个事情是FalseA or BTrue,因为其中一个是True,所以条件是True。现在,让我们看看最后一个运算符(暂时),not运算符。

not 运算符

not运算符让我们测试事物的相反情况。因此,如果A设置为True,那么not A就是False。就是这么简单。让我们通过以下代码看一些例子:

ch4_notOperator.py

A = True
B = False
print(not A)
print(not B)
print(not (A and B))
print(not (A or B))

从前面的代码中,我们已经讨论了这里的第一个打印语句。由于ATruenot AFalse。对于第二个print语句,我们期望结果是True,因为BFalse。现在,我们之前做了A and BA or B语句。我们知道A and BFalse,所以not (A and B)True。我们也知道A or BTrue,所以not (A or B)False

让我们看看程序打印了什么:

  • 对于not A,它打印如下内容:
False
  • 同样,对于not B,它打印如下内容:
True
  • 此外,对于not (A and B),它打印如下内容:
True
  • 最后,对于not (A or B),它打印如下内容:
False

在本节中,您已经了解了一些布尔运算符。使用布尔运算符,我们可以编写测试用例的算法,并根据这些情况提供输出。如前所述,程序将根据我们在算法中编写的指令运行。

通过使用这些运算符编写我们的算法,我们可以确保条件仅在我们希望它们适用的情况下应用。我们可以包括语句和提示来帮助产生正确的结果,而不是让程序在不正确的条件下运行。例如,如果距离的输入被意外输入为负数,布尔语句可以检查条件并在程序内为人们提供反馈,然后再次运行。使用布尔运算符提供清晰的逻辑过程,并允许更好和更清晰的算法。

现在我们已经看过基本运算符,很重要的是我们也要看看错误。识别逻辑错误可以帮助我们避免算法中的陷阱。

识别逻辑错误

在我们谈论太多逻辑错误之前,让我们谈谈为什么牢记它们的重要性。在 Python 中,并非所有错误都会导致程序失败或崩溃。一些逻辑错误将允许程序完全运行,而不会崩溃或警告用户发生错误。这些错误很难识别。

以下是一些可能让我们陷入麻烦的逻辑错误,但请记住,有许多方法可以无意中将逻辑错误纳入我们的程序中:

  • 在等式或语句中使用错误的变量

  • 使用错误的运算符来测试条件

  • 在检查条件时使用错误的缩进

我最内疚的是交换我的变量,但我也经常在缩进上犯错误。通常,当我尝试运行程序时,这些错误更容易被识别,因为在某些情况下程序可能无法运行。

让我们来看一个包含公式错误的简单算法。在第一个算法中,目标是在餐厅以每份 1.50 美元的价格购买一定数量的薯条后获得总费用:

ch4_Error1.py

number = int(input("Type the number of fries you are ordering: "))
cost = 1.50
total = number * number
print("Your total cost is $" + str(total) + ".")

如果我们运行上述程序,程序将无问题/错误地运行,并为12份薯条显示以下输出:

Type the number of fries you are ordering: 12
Your total cost is $144.

现在,如果我们注意到,我们会意识到 12 份薯条的费用为 144 美元太高了。这是因为我们的算法中存在错误。算法应该包含total = cost * number公式,如下所示:

ch4_Error1_fixed.py

number = int(input("Type the number of fries you are ordering: "))
cost = 1.50
total = cost * number
print("Your total cost is $" + str(total) + ".")

现在我们已经修复了该公式,输出是正确的:

Type the number of fries you are ordering: 12
Your total cost is $18.0.

如您所见,12 份薯条每份 1.50 美元,总共 18.0 美元更合理。

公式错误可能很难找到,特别是如果程序在不警告错误的情况下运行。如果我们有一个包含多个公式的大型算法,找到这些错误可能会变得繁琐和冗长。对此的最佳建议是在您能够的每个步骤中测试您的算法。这样,找到错误就变得更简单了。

现在让我们来看一下在测试条件时出现的错误。与公式错误类似,条件测试中的错误可能很难发现,因为程序可能仍然会运行:

ch4_Error2.py

number = int(input("Give a number between 1 and 200\. "))
if number > 99 and number % 2 == 0:
    print("That's a large, even number.")
elif number > 99 and number % 2 != 0:
    print("That's a large, odd number.")
elif number < 100 or number % 2 == 0:
    print("That's a small, even number.")
else:
    print("That's a small, odd number.")

在上面的代码中,算法中存在一个错误,导致我们在输入一些奇数时得到错误的反馈。看看第二个elif语句。那个or将产生一个错误。

如果我们运行这个程序,我们会得到一个输出。让我们用数字99来运行它:

Give a number between 1 and 200\. 99
That's a small, even number.

现在,问题在于99不是一个偶数。在算法的某个地方,我们在条件中引入了一个错误。在这种情况下,我们使用了or而不是and运算符:

elif number < 100 or number % 2 == 0:
    print("That's a small, even number.")

一旦我们用and替换or,我们就可以再次运行程序:

ch4_Error2_fixed.py

number = int(input("Give a number between 1 and 200\. "))
if number > 99 and number % 2 == 0:
    print("That's a large, even number.")
elif number > 99 and number % 2 != 0:
    print("That's a large, odd number.")
elif number < 100 and number % 2 == 0:
    print("That's a small, even number.")
else:
    print("That's a small, odd number.")

使用99作为输入,我们得到以下输出:

Give a number between 1 and 200\. 99
That's a small, odd number.

使用98作为输入运行程序,我们得到以下结果:

Give a number between 1 and 200\. 98
That's a small, even number.

如您所见,除非我们注意,否则我们可能会忽略条件和逻辑运算符中的错误。因为程序能够在我们的算法中运行这些错误,所以要捕捉到我们犯了错误的地方比起纳入会导致程序停止运行的错误要困难得多。

最后,让我们看一下使用相同的条件测试代码的缩进错误。这次,加入了一个缩进错误,我们得到了以下结果:

ch4_Error3.py

number = int(input("Give a number between 1 and 200\. "))
if number > 99 and number % 2 == 0:
    print("That's a large, even number.")
elif number > 99 and number % 2 != 0:
    print("That's a large, odd number.")
    elif number < 100 and number % 2 == 0:
        print("That's a small, even number.")
else:
    print("That's a small, odd number.")

在这种情况下,我们无法运行程序。第二个elif语句的缩进不正确。当我们尝试运行程序时,我们会收到一个“无效语法”错误消息。单击消息上的确定将带我们到代码,缩进错误会被突出显示,如下面的屏幕截图所示:

图 4.5 - 缩进错误

图 4.5 - 缩进错误

请注意elif语句下面的print()代码也缩进错误。一旦我们修复了这两个错误,我们就可以运行代码,就像我们在本章中之前做的那样。

将错误纳入我们的算法是一个常见的错误。正如您从前面的例子中看到的,识别一些错误可能很难,因为程序可能会像没有问题一样运行。

我可能没有在我的算法中捕捉到许多条件错误,但这可能只是因为我从未意识到一开始就有错误。这就是为什么运行我们的程序的各种实例以确保我们得到的结果是有意义的非常重要的原因之一。在本书中,我们将在查看程序和计算思维问题时讨论更多错误。与此同时,测试您的程序并经常测试。三次检查您的数学、缩进和逻辑。

总结

在本章中,我们讨论了归纳和演绎推理、逻辑推理、逻辑运算符和布尔逻辑。正如我们讨论的那样,大多数算法设计都属于演绎推理。我们学会了如何使用语句,如ifif-elseif-elif-else和嵌套语句,来编写测试条件的程序。

此外,我们了解到一些错误很难识别,因此验证我们的程序并经常测试它们是很重要的。

经过本章的学习,您现在更有能力使用逻辑推理编写算法。您还具备了在设计和规划算法时应用归纳和演绎推理的理解,并在算法中使用布尔逻辑和运算符的能力。您现在还能够通过识别可能的错误来测试您的算法,例如缩进错误、条件错误和公式错误。

在下一章中,我们将更深入地分析问题,使用计算思维元素来分解问题,以便我们可以创建有意义和有用的算法。

第五章:探索问题分析

在这一章中,我们将深入探讨问题分析,同时运用我们所学的一些知识,比如逻辑推理、布尔逻辑和算法设计。在这一章中,我们将通过问题定义、分解和分析来解决问题。

在本章中,我们将涵盖以下主题:

  • 理解问题定义

  • 学习分解问题

  • 分析问题

为了进一步理解问题,我们需要看一个更复杂的问题,并定义它,以便开始算法设计过程。在本章中,您将学习如何定义问题并分解问题,以便设计算法。在这个过程中,您还将学习 Python 中的字典。阅读完本章后,您将能够使用计算思维过程来设计和创建解决复杂问题的算法。

技术要求

您需要最新版本的 Python 来运行本章的代码。您可以在此处找到本章使用的完整源代码:github.com/PacktPublishing/Applied-Computational-Thinking-with-Python/tree/master/Chapter05

理解问题定义

正如我们在第二章中讨论的,计算思维的要素,计算思维使用四个要素来解决问题:

  • 问题分解:这是将数据分解的过程。

  • 模式识别:这是找到相似性或模式的过程。

  • 抽象:这个元素处理泛化模式。

  • 算法设计:这是我们为解决问题定义一组指令的地方。

在本节中,为了更多地了解如何分析问题,我们将分析一个更大的问题,并经过需要创建算法的步骤。为了能够创建算法,我们必须分析问题,并清楚地确定我们要解决什么问题。也就是说,我们的算法是为了什么? 为什么我们需要构建它? 查看问题的分解,然后定义我们需要的东西,将在最后为我们提供更好的算法。

我们将在下一节中解决一个问题。

问题 5A – 创建一个在线商店

让我们来看看以下问题。您正在开设一家在线商店。它还处于起步阶段,但您将有三种不同类型的商品可供选择。它们是钥匙扣、水瓶和 T 恤。对于这个特定的问题,我们将经历一个三步过程:

  1. 做出假设

  2. 需要考虑的事项

  3. 构建字典

我们将在接下来的部分中看到前面的步骤。

做出假设

让我陈述一些关于我们将要使用的这家商店的假设:

  • 这是一家为客户提供商品的公司,供客户与他们的客户分享。

  • 每个项目都可以有标志和/或个性化信息,比如姓名、电子邮件和电话号码。

现在我们将进入下一节,讨论需要考虑的事项。

需要考虑的事项

现在让我们在开始制定算法之前,看看您需要考虑的一些事项:

  • 商品是否个性化?

  • 个性化是否按字符、行或项目收费?

  • 价格是固定的,还是当客户批量购买时会发生变化?

  • 如果客户订购多种类型的商品,是否会有折扣?

  • 每个项目的基准价格是多少?

前面的观点并不是我们可以讨论的唯一问题。但当我们分解问题时,这些是我们将开始研究的问题。

在那之前,让我们讨论一下如何在程序中为每个项目包含信息。如果你还记得[第三章](B15413_03_Final_SK_ePub.xhtml#_idTextAnchor056),理解算法和算法思维,我们可以使用Python中的字典来保存我们的商品菜单。在这种情况下,我们有钥匙扣、水瓶和 T 恤。

建立一个字典

在我们看这个问题所提出的复杂性并分解信息之前,我们可以建立自己的字典。我们可以使字典中每个项目的价格都是基础价格(不包含任何定制或折扣的价格),如下所示:

  • 每个钥匙扣的成本:$0.75

  • T 恤成本:$8.50

  • 每个水瓶的成本:$10.00

现在让我们建立字典。记住,你可以在没有字典的情况下做到这一点,但是创建一个字典可以让你在以后有必要时更新定价。你也可以创建函数来解决这个问题。我们正在使用逻辑和字典来解决这个问题。以下代码向你展示了如何建立一个字典:

ch5_storeDictionary.py

online_store = {
    'keychain': 0.75,
    'tshirt': 8.50,
    'bottle': 10.00
    }
print(online_store)

从前面的代码片段中,请记住这里不需要print()函数,但我经常使用它来确保代码在我继续构建算法的同时能正常工作。还要注意变量的名称——keychaintshirtbottle——都是简化的。

这是输出的样子:

{'keychain': 0.75, 'tshirt': 8.5, 'bottle': 10.0}

输出显示给我的是每个变量的价格都被正确保存了。我使用print函数来测试我的字典,并确保它在我开始从字典中获取所需内容之前能够正确运行。

这有助于我们在编写代码时重复使用变量。拥有这些简单易识别的变量将允许我们在不添加错误的情况下更改和添加算法。

在本节中,我们了解到问题分析和定义帮助我们确定如何最好地设计我们的解决方案。记住,当我们面对问题时,我们在编写算法之前和算法中使用的定义对于我们的设计和最终产品至关重要。现在让我们来看看问题的分解。

学习分解问题

当我们分解问题时,我们正在确定算法需要为我们提供什么。最终用户需要看到一些无缝的东西。看看图 5.1中的流程图;这是一个基本的决策流程图,帮助我们设计我们的算法。

首先让我们做另一个假设,即,如果用户输入超过 10,价格将会更低。在这种情况下,我们只会做少于 10 或大于或等于 10。然而,如果你需要进一步细分,你可以添加更多情况,比如以下情况:

  • 少于或等于 10

  • 超过 10 但小于或等于 50

  • 50 或更多

你可以有尽可能多的情况。对于这个算法,我们将保持两种情况,因为我们还必须包括个性化成本,我们不想创建一个过于复杂的算法。

下图向你展示了算法的流程图:

图 5.1 – 初始决策流程图

图 5.1 – 初始决策流程图

正如你在前面的图表中所看到的,这不是一个完成的流程图。在我们做出关于 T 恤的决定之后,我们需要转向瓶子。我们如何编写算法将取决于我们想要输出什么。现在,我们提供给用户的是当他们结账离开你创建的在线商店时会得到的信息。

在下一节中,让我们使用前面的流程图来创建一个算法。

将流程图转换为算法

图 5.1中的图表使我们能够查看我们正在编写的算法的决策过程。在编写算法时,我们将要关注以下关键点:

  • 字典和输入:输入可以在算法内部或由用户输入;字典是在算法内部构建的。这意味着,要使用字典,我们必须在算法中定义它,然后才能使用它。

  • 成本:这是每件物品的基本成本。

  • 个性化成本:这是添加到基本成本中的成本。

我们现在将在接下来的部分详细查看前面的要点。

构建字典并提供输入

在添加任何复杂性之前,让我们看看如何获取每件物品的价格并将其用作基本价格。我们需要计算每种物品的数量。以下代码向您展示了这一点:

ch5_storeQuantities.py

online_store = {
    'keychain': 0.75,
    'tshirt': 8.50,
    'bottle': 10.00
    }
keychain = online_store['keychain']
tshirt = online_store['tshirt']
bottle = online_store['bottle']
choicekey = int(input('How many keychains will you be purchasing? If not purchasing keychains, enter 0\. '))
choicetshirt = int(input('How many t-shirts will you be purchasing? If not purchasing t-shirts, enter 0\. '))
choicebottle = int(input('How many t-shirts will you be purchasing? If not purchasing water bottles, enter 0\. '))
print('You are purchasing ' + str(choicekey) + ' keychains, ' + str(choicetshirt) + ' t-shirts, and ' + str(choicebottle) + ' water bottles.')

从前面的代码片段中可以看到,我们在字典下添加了变量。这将在以后很有用。这些变量被命名为choicekeychoicetshirtchoicebottle。命名变量使我们能够返回并根据需要更改代码。在这种情况下,每个变量都要求从运行程序的人那里获取输入,以获取他们订购的钥匙扣、T 恤和水瓶的数量。再次强调,解决这个问题有多种方法,但我们正在利用到目前为止学到的知识来创建一个算法解决方案。

当我们对3个钥匙扣、0件 T 恤和10个水瓶运行前面的代码时,这是我们的输出:

How many keychains will you be purchasing? If not purchasing keychains, enter 0\. 3
How many t-shirts will you be purchasing? If not purchasing t-shirts, enter 0\. 0
How many t-shirts will you be purchasing? If not purchasing water bottles, enter 0\. 10
You are purchasing 3 keychains, 0 t-shirts, and 10 water bottles.

正如您所看到的,我们有一个接受用户输入的程序,然后向用户确认他们为每件物品所做的选择。

让我们看看关于成本的下一节。

对成本进行更改

现在让我们添加成本的变化。假设如果顾客购买超过 10 件物品,那么更新后的成本如下:

  • 钥匙扣:$0.65

  • T 恤:$8.00

  • 水瓶:$8.75

为了进行前述更改,我们可以让程序更新成本差异,如下所示:

ch5_storeCost.py

online_store = {
    'keychain': 0.75,
    'tshirt': 8.50,
    'bottle': 10.00
    }
choicekey = int(input('How many keychains will you be purchasing? If not purchasing keychains, enter 0\. '))
choicetshirt = int(input('How many t-shirts will you be purchasing? If not purchasing t-shirts, enter 0\. '))
choicebottle = int(input('How many t-shirts will you be purchasing? If not purchasing water bottles, enter 0\. '))
print('You are purchasing ' + str(choicekey) + ' keychains, ' + str(choicetshirt) + ' t-shirts, and ' + str(choicebottle) + ' water bottles.')
if choicekey > 9:
    online_store['keychain'] = 0.65
if choicetshirt > 9:
    online_store['tshirt'] = 8.00
if choicebottle > 9:
    online_store['bottle'] = 8.75
keychain = online_store['keychain']
tshirt = online_store['tshirt']
bottle = online_store['bottle']
print(online_store)

现在我们已经更新了代码,我想打印出我的进展,以确保代码正常工作并进行更改。在这种情况下,我想确保如果总数大于 10,成本会更新。(也就是说,当顾客订购超过 10 件物品时,它会将每件物品的成本更新为更低的成本。)前面代码的输出如下:

How many keychains will you be purchasing? If not purchasing keychains, enter 0\. 10
How many t-shirts will you be purchasing? If not purchasing t-shirts, enter 0\. 14
How many t-shirts will you be purchasing? If not purchasing water bottles, enter 0\. 10
You are purchasing 10 keychains, 14 t-shirts, and 10 water bottles.
{'keychain': 0.65, 'tshirt': 8.0, 'bottle': 8.75}

您现在可以从前面的输出中看到,字典已根据用户提供的总数进行了更新。

现在我们需要继续并提供成本。我们可以提供总物品成本或整个购买的总成本,或两者(让我们两者都做)。看一下以下代码片段:

ch5_storeTotals.py

keychain = online_store['keychain']
tshirt = online_store['tshirt']
bottle = online_store['bottle']
print('You are purchasing ' + str(choicekey) + ' keychains, ' + str(choicetshirt) + ' t-shirts, and ' + str(choicebottle) + ' water bottles.')

前面的代码片段被添加,以便我们可以有一个print语句来确认用户的输入。通过在代码末尾打印该语句,我们正在与用户核对程序是否能正确读取数字,以及用户是否输入了正确的数字。

我们可以继续编写代码以添加每件物品的成本:

ch5_storeTotals.py

totalkey = choicekey * keychain
totaltshirt = choicetshirt * tshirt
totalbottle = choicebottle * bottle
grandtotal = totalkey + totaltshirt + totalbottle
print('Keychain total: $' + str(totalkey))
print('T-shirt total: $' + str(totaltshirt))
print('Water bottle total: $' + str(totalbottle))
print('Your order total: $' + str(grandtotal))

在前面的代码片段的末尾的print语句提供了每件物品的总成本以及整个订单的总成本的细目。在询问所有物品的输入后,代码然后打印每件物品成本的小计。前面代码的结果如下:

How many keychains will you be purchasing? If not purchasing keychains, enter 0\. 10
How many t-shirts will you be purchasing? If not purchasing t-shirts, enter 0\. 7
How many t-shirts will you be purchasing? If not purchasing water bottles, enter 0\. 14
You are purchasing 10 keychains, 7 t-shirts, and 14 water bottles.
Keychain total: $6.5
T-shirt total: $59.5
Water bottle total: $122.5
Your order total: $188.5

现在我们已经有了没有个性化的物品总数,我们需要考虑如果订购了个性化,需要考虑个性化的成本。

在下一节中,让我们看看个性化成本是多少,以及在继续之前我们需要做出的决定。

添加个性化

目前,让我们将钥匙扣、T 恤和水瓶的个性化限制为二进制问题,即用户要么想要个性化,要么不想要。我们不考虑个性化的分层成本,这可能是您见过的。如果您想要添加分层,您需要做出更多决策,比如选择字体的成本,个性化的长度等等。目前我们不考虑这些,但随时可以添加到这段代码中以解决这些类型的自定义。让我们为个性化添加另一个假设:

  • 1 美元的钥匙扣

  • T 恤 5 美元

  • 水瓶 7.50 美元

我们需要创建前述条件,然后将它们实施到我们的变量中。让我们逐部分查看代码。以下文件包含我们将分解的每个部分。

回想一下,我们的算法首先要求输入购买物品的数量。以下代码片段获取用户输入,以便考虑个性化:

ch5_storePersonalize.py

perskey = input('Will you personalize the keychains for an additional $1.00 each? Type yes or no. ')
perstshirt = input('Will you personalize the t-shirts for an additional $5.00 each? Type yes or no. ')
persbottle = input('Will you personalize the water bottles for an additional $7.50 each? Type yes or no. ')
if perskey == ('yes' or 'Yes'):
    online_store['keychain'] = online_store['keychain'] + 1.00
if perstshirt == ('yes' or 'Yes'):
    online_store['tshirt'] = online_store['tshirt'] + 5.00
if persbottle == ('yes' or 'Yes'):
    online_store['bottle'] = online_store['bottle'] + 7.50
keychain = online_store['keychain']
tshirt = online_store['tshirt']
bottle = online_store['bottle']
totalkey = choicekey * keychain
totaltshirt = choicetshirt * tshirt
totalbottle = choicebottle * bottle
grandtotal = totalkey + totaltshirt + totalbottle

前面的代码片段询问用户关于个性化的二进制问题。在获取输入后,代码根据用户输入做出一些决策,并定义keychaintshirtbottle变量以及选择的总数。接下来的代码片段使用总数打印出每个购买物品的信息以及最终总数:

print('Keychain total: $' + str(totalkey))
print('T-shirt total: $' + str(totaltshirt))
print('Water bottle total: $' + str(totalbottle))
print('Your order total: $' + str(grandtotal))

从前面的代码中可以注意到,keychaintshirtbottle变量是在基于总数和个性化的自定义之后定义的。记住,在算法设计中,顺序很重要。如果我们在程序中较早地定位这些变量,随后的个性化等条件将不会影响这些变量。

因此,为了能够获得我们变量所需的一切,我们需要在定义一些影响它们的条件之后定义它们,比如自定义。看一下前面的代码,注意变量的定义位置。随时可以通过更改变量定义的位置来玩弄代码,看看最终结果是否会改变。

这是一个具有钥匙扣决策过程的视觉流程图:

图 5.2 - 钥匙扣决策流程

图 5.2 - 钥匙扣决策流程

正如您从前面的图表中所看到的,这仅适用于钥匙扣。我们需要为另外两个变量重复这个过程。在图表中,您可以看到物品的决策过程。首先,用户指示购买的物品数量,然后指示他们是否会个性化。

根据每个答案,程序计算总数。例如,如果没有个性化,总数会在决策树中更早地计算出来。我们可以使用函数(如我之前提到的)重写这个程序,以简化一些过程。目前,我们专注于学习如何分解问题,分析条件,以及如何设计算法以考虑多个决策。记得完成其他物品的图表,以便在设计算法时决策过程更容易编码。

在本节中,我们学习了如何使用流程图创建算法。我们还学习了为我们的算法构建字典。

在我们继续之前,让我们看看分析问题的过程。在创建这个算法时,我们分解了问题。然而,在我们下一章之前,有一些问题分析的关键组成部分值得我们考虑。

分析问题

在分析问题时,有一些步骤可以帮助我们确保我们正在创建最佳算法:

  • 清楚地阅读和理解问题。

  • 确定解决方案的主要目的。

  • 确定问题的约束。

  • 确定决策流程。

  • 建立可能解决问题的算法。

  • 为问题确定最佳的算法工具。

  • 经常测试算法片段。

  • 验证算法是否提供了已确定问题的解决方案。

如果我们回到我们的问题,我们在整个章节中都经历了这个过程:

  • 我们有一个有三种商品的在线商店。

  • 商品成本取决于购买数量。

  • 商品价格也取决于个性化定制。

  • 我们创建了流程图来帮助我们确定决策过程以及如何编写代码。

  • 我们通过代码行验证了我们的代码,这些代码行允许我们多次检查算法是否产生了正确的响应。

  • 根据需要重新访问和重新排序代码片段。

  • 我们验证了算法的输出是否符合我们所确定的问题。

前面的过程需要重复,也就是说,这不是一个线性过程。有时我们会编写一个算法,然后重新访问决策流程图,进行调整,然后再次处理算法。

在处理更大的问题时,分析我们的问题在多个停止点变得更加清晰。我们应该在测试之前写上几百行代码吗? 不!想象一下有 300 行代码,只在第 20 行发现一个错误,这个错误会一直传递到算法的其余部分。

在每个可能的进度点进行测试将使我们能够发现长期成本的小错误。记住,第一次几乎不可能编写完美的算法。我们都会犯错误,无论大小,所以继续测试和分析我们的进展非常重要。

在离开本章之前,让我们再看一个问题并再次经历这个过程。

问题 5B - 分析一个简单的游戏问题

你想设计一个猜数字游戏。用户必须猜一个随机数。

让我们从定义我们的问题开始,这种情况下是一个游戏。让我们确定已知信息:

  • 计算机需要随机选择一个数字。

  • 用户需要输入一个数字。

  • 计算机将不得不检查用户输入是否与随机生成的数字匹配。

现在,这还不够!如果我第一次猜不中,我会输吗?我有多少次机会?随机数是 1 到 10 之间还是 1 到 500 之间? 在我们开始编码之前,我们需要做一些决定。让我们添加一些参数:

  • 数字在 1 到 100 之间。

  • 用户将有 5 次猜测的机会。

  • 计算机会告诉用户答案是太高还是太低。

现在我们有了这些参数,我们可以创建一个决策流程图:

图 5.3 - 猜数字游戏的决策流程图

图 5.3 - 猜数字游戏的决策流程图

从前面的图表中可以看出,图表并不完整。这是因为我们将使用一些逻辑使过程重复 5 次。我们稍后会详细介绍。现在,请注意决策。首先,程序生成一个数字(但不会显示)。然后用户输入一个猜测,要么正确,要么错误。如果正确,用户就赢得了游戏。如果不正确,程序会告诉用户答案是太低还是太高,并要求新的猜测。然后过程将根据需要重复。现在,让我们编写算法。

首先,让我们生成随机数并让用户猜测。为随机生成的数字和用户输入添加print()函数,以便您可以看到信息是否正常工作。记住,我们以后会把它们拿掉,但是反复检查我们的代码作为问题分析过程的一部分非常重要。以下代码将执行相同的操作:

ch5_guess1.py

import random as rand
compnumber = rand.randint(1, 100)
print(compnumber)
usernumber = int(input('Choose a number between 1 and 100\. You'll get 5 guesses or you lose! '))
print(usernumber)

你会注意到前面的代码中导入了random模块。我们还将其导入为rand。这只是为了节省时间和空间。在 Python 中,当你导入一个模块时,你可以给它重命名。random模块给了我们一个在我们选择的范围内生成数字的方法。

rand.randint(1, 100)代码行包括1100。这些是随机数生成器的端点或限制。rand函数是指模块,如前所述,而randint(a, b)指的是ab之间的随机整数(包括ab)。

多次运行代码,看看计算机生成的数字每次都会发生变化。以下是三个测试案例:

  • 以下是前面代码的测试案例 1:
27
Choose a number between 1 and 100\. You'll get 5 guesses or you lose! 10
10

如你从前面的输出中看到的,27是计算机生成的随机数,而10是用户输入的数字。

  • 以下是前面代码的测试案例 2 的结果:
68
Choose a number between 1 and 100\. You'll get 5 guesses or you lose! 65
65

如你从前面代码的输出中看到的,68compnumber变量的值,而用户(我)输入的数字是65。如此接近,但又如此遥远!

  • 以下是测试案例 3 的输出:
50
Choose a number between 1 and 100\. You'll get 5 guesses or you lose! 23
23

正如你从前面的输出中看到的,计算机选择了数字50,而用户输入了23

对于我们游戏的最终版本,我们不会打印出计算机的数字。那是作弊!现在,我们只是在测试。

让我们再添加一个条件,即第一次猜测是否正确。为此,我们将必须验证compnumber == usernumber。在进入额外的重复和逻辑之前,我们将再次测试这一点,所以我们只需说如果是真的,那么你赢了;如果是假的,那么你输了:

ch5_guess2.py

import random as rand
compnumber = rand.randint(1, 100)
usernumber = int(input('Choose a number between 1 and 100\. You'll get 5 guesses or you lose! '))
if compnumber == usernumber:
    print('You win!')
else:
    print('You lose!')

让我们说我在第一次尝试时失败了。但是我不会放弃,因为这可能需要 100 次或更多次。当你运行程序时,它看起来像这样:

Choose a number between 1 and 100\. You'll get 5 guesses or you lose! 35
You lose!

现在让我们谈谈重复一行代码。我们给用户 5 次猜测。我们如何在 Python 中做到这一点?

在 Python 中,我们可以使用for循环来迭代代码。我们知道我们有 5 次猜测,所以我们将使用类似for number in range(5):这样的东西来开始我们的逻辑,如下面的代码所示:

ch5_guess3.py

import random as rand
compnumber = rand.randint(1, 100)
i = 5
for number in range(5):
    usernumber = int(input('Choose a number between 1 and 100\. You have ' + str(i) + ' guesses left. '))
    if compnumber == usernumber:
        print('You win!')
    else:
        i = i - 1
print('You're out of guesses! You lose! ')

从前面的代码中,你注意到i变量了吗?我们使用这个变量,这样用户就知道他们还剩下多少次猜测。所以如果我们有 5 次猜测,代码将从i = 5开始;然后,如果用户猜错了,它将使用i = i - 1这一行,提醒用户他们现在还剩下 4 次猜测,依此类推。看看我们运行该程序时会发生什么:

Choose a number between 1 and 100\. You have 5 guesses left. 14
Choose a number between 1 and 100\. You have 4 guesses left. 98
Choose a number between 1 and 100\. You have 3 guesses left. 48
Choose a number between 1 and 100\. You have 2 guesses left. 12
Choose a number between 1 and 100\. You have 1 guesses left. 54
You're out of guesses! You lose!

现在,我们并不是真的很公平。如前所述,我们希望每次用户尝试猜测时都给出一个提示。现在我们有了检查它们是否相等的条件,我们可以添加一个elif条件来检查它是大还是小。下面的代码显示了这一点:

ch5_guess4.py

import random as rand
compnumber = rand.randint(1, 100)
i = 5
for number in range(5):
    usernumber = int(input('Choose a number between 1 and 100\. You have ' + str(i) + ' guesses left. '))
    if compnumber == usernumber:
        print('You win!')
        exit()
    elif compnumber > usernumber:
        print('Your number is too small!')
        i = i - 1
    elif compnumber < usernumber:
        print('Your number is too large!')
        i = i - 1
print('You're out of guesses! You lose! ')

前面的代码现在为用户提供了一些反馈。如果数字大于计算机生成的数字,用户会收到反馈'你的数字太大了!',如果用户的数字小于计算机生成的数字,那么他们会收到反馈'你的数字太小了!'。如果用户赢了,我们还使用了exit()代码。这是因为我们希望游戏在我们赢了时停止。

这给了我们一个战胜这个游戏的机会,看看现在的输出是什么样子:

Choose a number between 1 and 100\. You have 5 guesses left. 50
Your number is too small!
Choose a number between 1 and 100\. You have 4 guesses left. 75
Your number is too large!
Choose a number between 1 and 100\. You have 3 guesses left. 65
Your number is too small!
Choose a number between 1 and 100\. You have 2 guesses left. 70
Your number is too large!
Choose a number between 1 and 100\. You have 1 guesses left. 68
You win!

现在看看当我们输掉游戏时会发生什么:

Choose a number between 1 and 100\. You have 5 guesses left. 10
Your number is too small!
Choose a number between 1 and 100\. You have 4 guesses left. 40
Your number is too large!
Choose a number between 1 and 100\. You have 3 guesses left. 20
Your number is too small!
Choose a number between 1 and 100\. You have 2 guesses left. 30
Your number is too small!
Choose a number between 1 and 100\. You have 1 guesses left. 35
Your number is too large!
You're out of guesses! You lose!

正如你所看到的,你得到了一个不同的最终消息。我承认我尝试了很多次才赢得了一场比赛,所以我才得到了下面的输出,但你可以看到第二次猜测是正确的游戏:

Choose a number between 1 and 100\. You have 5 guesses left. 10
Your number is too small!
Choose a number between 1 and 100\. You have 4 guesses left. 90
You win!

我们将用最后一个算法来停止这个游戏。如果我们愿意,我们实际上可以让这个游戏变得更好,但它完成了我们需要它完成的工作。你可以考虑对你的游戏进行一些改变,如下所示:

  • 添加一个选项,提醒用户已经猜过的数字。

  • 增加一个选项,提醒用户忽略了先前的提示(所以如果用户给出一个太小的数字,然后给出一个更小的数字,电脑会提醒他们)。

我相信你可以尝试更多的定制。但现在,我们经历了那个问题,并遵循了我们在分析问题时应该考虑的要点:

  1. 我们阅读并理解了问题。

  2. 我们确定了目的——创建一个电脑玩家对用户玩家的猜数字游戏。

  3. 我们确定了问题的约束条件——数字范围、猜测次数和提供提示。

  4. 我们创建了一个决策流程图。

  5. 我们编写并建立了解决问题的算法。

  6. 我们看了如何创建一个简单的算法,可以迭代而不必为每个条件单独编写。

  7. 我们在多个点测试了算法。

  8. 我们验证了算法对于胜利和失败的运行是否准确。

在这里你看不到的是我在展示算法之前经历了多少错误。在写作过程中,我不得不使用前面的步骤来帮助我识别错误,检查最佳算法,并迭代程序。这是一个我们将继续使用的过程。

总结

在本章中,我们讨论了问题定义、分解和分析。我们使用问题来帮助我们经历识别问题、将其分解为相关部分和确定约束条件的过程,并分析我们的算法。我们使用流程图来帮助我们学习在设计算法时的决策过程以及如何组织思路。

我们学会了经常测试我们的算法。这为我们提供了识别错误的技能和理解,而不是等到我们有太多行代码时再等待。我们使用了一个在线商店和一个猜数字游戏来帮助我们了解 Python 中的一些功能。在整个过程中,我们使用布尔代码来验证输入,我们使用嵌套的if语句,我们学会了如何在解决所提出的问题时使用字典。

此外,我们有机会使用字典来处理使用用户输入和变量的算法。使用该算法使我们能够灵活定义一些变量,并在运行算法时或在算法内部编辑变量。

在下一章中,我们将深入探讨解决方案的过程和设计,更深入地研究更复杂的问题和 Python 语言。

第六章:设计解决方案和解决方案过程

在本章中,我们将使用先前学到的内容,如问题的分析和计算思维过程,来设计解决方案。我们将结合逻辑处理来创建决策过程的视觉表示,以指导我们的算法设计。讨论的视觉表示包括图表流程图和其他有用的过程。在本章中,我们将学习解决方案设计的关键要素;如何创建、使用和应用图表来处理和设计解决方案;我们将看看如何将解决方案设计过程应用于各种问题。

在本章中,我们将涵盖以下主题:

  • 设计解决方案

  • 绘制解决方案

  • 创建解决方案

为了进一步了解算法和解决方案设计,我们需要更仔细地研究问题的前端。我们将从深入讨论设计解决方案的过程开始。

技术要求

您将需要最新版本的 Python 来运行本章中的代码。您可以在此处找到本章中使用的完整源代码:github.com/PacktPublishing/Applied-Computational-Thinking-with-Python/tree/master/Chapter06

设计解决方案

当我们设计解决方案时,我们经常使用设计思维模型,即使我们并不总是意识到。设计思维由不同的模型描述,但我们将看到最常见的五步模型。

与计算思维结合,设计思维过程可以帮助我们在开始绘制解决方案之前发展我们的想法。需要注意的是,我们不像在计算思维中那样线性地进行设计思维过程。想想计算思维的步骤:

  • 问题分解

  • 模式识别

  • 抽象

  • 算法设计

我们在之前的章节中定义了所有这些步骤,最近是在第五章的介绍中,探索问题分析。再次看到它们,我们知道在编写和设计算法时可以返回到分解。这就是我们所说的非线性过程。

设计思维模型也是如此。它是由斯坦福大学哈索-普拉特纳设计学院设计的。该模型的主要步骤包括以下内容:

  • 共情:从受众或利益相关者的角度理解问题。

  • 定义:确定目标、需要做出的决策、引入的任何偏见以及与问题相关的任何细节。

  • 构思:进行头脑风暴,与我们将在本章的下一节中进行的图表化工作相呼应。

  • 原型:设计算法解决方案并经常检查它。

  • 测试:在整个过程中经常检查您的算法,并根据需要返回到以前的步骤。

正如您所看到的,我已经调整了设计思维模型,使其更符合计算思维过程。当我们使用这些模型并将它们结合在一起时,主要目标是将更难的问题分解为更简单的部分,以便我们可以设计和解决最佳算法。这并不取代计算思维。它只是提供了更好的想法,让我们了解如何处理这个过程。以下图表可以帮助演示这个过程可能如何工作:

图 6.1 - 设计思维模型

图 6.1 - 设计思维模型

正如您所看到的,与最常见的线性模型不同,前面的模型将过程显示为循环。也就是说,回到共情可以发生在任何时候,因此在这些步骤之间来回移动是使用设计思维模型的最佳方式。

让我们看一个场景,在这个场景中,我们将设计思维与计算思维结合使用。

问题 1-营销调查

假设您正在与一家营销公司合作,他们要求您设计一份调查,以收集有关网站的反馈。以下是您可能要经历的一些步骤:

  1. 确定利益相关者:这包括您将调查的人员以及调查后将使用信息的人员,例如。

  2. 确定问题:这是您定义希望从调查中获得的信息的地方。

  3. 设计调查:这不仅包括您确定的问题,还包括调查的美学外观。

  4. 信息收集:这是您决定如何与将填写调查的人沟通的地方,例如电子邮件、网站上的链接或类似方式。

  5. 数据分析:您可以编写 Python 算法来帮助您进行数据分析,包括根据收集的数据创建表格和图表。

  6. 数据共享:这是您将计划向最初的利益相关者展示的可视化、报告和数据呈现的地方。

让我们明确一点:这是对过程的过度简化。但是假设您意识到需要为调查增加另一组。比如,最初只从学校的学生那里获得反馈,但后来意识到您想要增加教师和家长。那么,您将回到步骤 1,确定您的其他信息将受到哪些影响。您可能想要改变调查的外观,或者为成年人和儿童添加不同的调查。您可能需要添加只针对某一组的问题,这会影响您在调查算法中的决策。

现在让我们看看设计思维过程中的这些步骤。

对于我们的问题,确定利益相关者和问题是设计思维模型的步骤 123的一部分:共情定义构思。构建算法既是原型测试的一部分,也是步骤 45。将人员添加到调查中会让我们回到步骤 1-3。循环重复,直到我们为我们的情景拥有一个可行的算法。在整个计算思维模型中,并使用其中的元素,您将使用嵌入其中的设计思维过程。这是决策过程的自然部分。

现在我们已经看过设计思维模型,让我们来看看如何使用图解解决方案来直观地表示决策过程。

图解解决方案

当我们设计算法时,我们经常使用图表和流程图来帮助我们分析过程,并直观地看到我们的决策是在哪里做出的。这些图表使我们能够创建更好的算法。您会记得,在第五章中,当我们正在建立一个商店时,我们创建了一个流程图(图 5.1图 5.2)。

创建这些图表的过程因开发人员或编码人员而异。例如,我通常会为问题创建头脑风暴,然后从中制作流程图。为了了解这个过程,让我们回到本章前面的调查问题。看看以下头脑风暴。它并不完整,因为您可以添加许多子主题。这个头脑风暴假设我们正在调查利益相关者,以评估和分享对学校网站的反馈。

图 6.2 -头脑风暴图

图 6.2 -头脑风暴图

从图表中可以看出,有许多考虑要做。实际的调查设计可能由我们作为程序员提供,或者我们可能参与设计调查。如果我们有了调查,我们的头脑风暴可能会有所不同,因为我们在问题中导航,并决定如何最好地将它们放置在算法中。这是共情过程的一部分。我们从多个角度、多个利益相关者的角度来看待我们的信息,并决定如何编写算法来帮助我们达到我们的目标。头脑风暴这样的非正式图表的目的是让我们在尝试创建更详细和有组织的流程图之前开始组织想法。当我们工作在图表上时,我们正在定义和构思我们的算法。这就是为什么在开始直接编码之前勾画出我们的计划是很重要的。

关于流程图,当我们讨论在 Python 中创建商店时,上一章中我们看到了一些流程图。现在让我们看一下基于一些决策的决策流程图。

重要的是要注意,从零开始创建调查可能是困难的。部分原因是可能有依赖于彼此的问题。例如,假设你要求用户说明他们是否赞成颜色选择。如果他们赞成,你可以继续。但如果他们不赞成,你可能想提供其他颜色方案供审查。这个问题只会出现在选择选项的人身上。如果我们要处理头脑风暴的所有信息,我们的流程图可能会相当复杂,所以我们将专注于头脑风暴的外观类别中的一些问题。看一下下面的流程图:

图 6.3 - 调查的一个元素的流程图

图 6.3 - 调查的一个元素的流程图

从流程图中可以看出,有些事情并不清晰,比如完成一个问题后会发生什么,每个决定后你会去哪里等等。当我创建流程图时,有时会添加箭头来帮助我看到每个步骤之后会发生什么。下面的流程图显示了添加的一些箭头:

图 6.4 - 带箭头的流程图

图 6.4 - 带箭头的流程图

从前面的流程图可以看出,并非所有箭头都被添加,但要仔细看颜色方案。如果用户同意颜色方案,那么他们直接进入字体部分。如果他们不同意,他们会被显示选项。假设一次只显示一个选项,那么用户在选择喜欢的选项后会进入字体部分。还可以添加提示,询问用户是否想再次查看选项,这会将他们带回备选方案 1。箭头可以被添加以显示这些细节。

这完全取决于作为开发人员和程序员的你自己最容易理解的方式。如果你是作家,把这些当作你的日记笔记。你组织想法的方式可能是个人的,只要确保你的最终结果和算法可以被意图使用的人轻松使用。

现在让我们看看如何将所有内容整合在一起,并为一些问题设计解决方案。

创建解决方案

当我们面对问题时,我们希望创建解决方案,解决我们所提供的信息,并提供一切所需并且易于用户理解的算法。在本节中,我们将利用本章学到的内容来设计解决问题的解决方案。

当我们使用我们的头脑风暴和流程图创建这些解决方案时,我们应该考虑以下内容:

  • 我们计划的解决方案是否解决了问题?

  • 解决方案设计是否显示了算法成功的清晰路径?

如果对这些问题的答案是“是”,那么我们可以开始编写解决方案。请记住,我们需要尽可能经常地测试算法。在编写算法时,请记住以下几点:

  • 添加注释以标识您可能需要返回的部分,并清楚地帮助识别和定义您的变量、字典、函数和任何关键组件。

  • 检查一下您是否有任何错误,比如第五章**中讨论的那些错误,探索问题分析。

  • 尽可能经常运行您的程序以测试错误。

对于解决方案过程,我们将使用一个与本章早期工作的调查略有不同的问题。随着我们在本书中的学习,我们将解决您可以用于该问题的组件,比如添加图像、显示图形等。但是现在,让我们继续使用一些更基本的 Python 来练习创建解决方案的过程。

问题 2 - 比萨订单

我知道 - 食物。但这是演示逻辑和算法创建的最佳方式之一,所以请耐心等待。假设我们有一家比萨店。我们只卖一种类型的面团,因为我们是一种特色的地方。我们有两种不同尺寸的比萨:个人和家庭。有两种酱料选项:番茄酱和大蒜奶油。有三种奶酪选项:无奶酪、普通奶酪和额外奶酪。

选择的五种配料有:蘑菇、意大利辣香肠、洋葱和辣椒。不,我们的比萨店不会放橄榄。

让我们解决这个问题。我们希望有一个算法来捕捉用户选择的比萨订单选项。我们现在不考虑成本和订单中的其他项目,比如额外的比萨、饮料、甜点等。

这是我们知道的:

  • 尺寸:个人或家庭

  • 酱料:番茄酱或大蒜奶油

  • 奶酪:无奶酪、普通奶酪、额外奶酪

  • 配料:蘑菇、意大利辣香肠、洋葱、辣椒

现在我们有了这些,让我们看一下带有信息的流程图:

图 6.5 - 比萨店决策流程图

图 6.5 - 比萨店决策流程图

正如您所看到的,该图显示了这个特定问题的一个相当线性决策过程。我们还没有考虑的一件事是询问用户是否希望进行任何更改。这可能需要在每个步骤中发生。比如在选择奶酪时改变主意,改为选择番茄酱而不是大蒜奶油酱。您需要有一种方法可以返回,因此我们需要在创建算法时牢记这一点。

请记住,我们目前仅使用文本代码,因此我们现在将使用用户的数字和字母输入。但是,有办法将 Python 整合到更强大的算法中,这些算法包括图像、按钮等。

看一下算法中的以下代码片段:

ch6_pizzeria.py

#Get input for your variables for size and sauce first. 
size_choice = str(input("Is this a personal or family pizza? Type personal or family. "))
sauce_choice = str(input("Which sauce would you like? Marinara or garlic cream? Type m for marinara and g for garlic cream. "))
if sauce_choice == "g":
    sauce = "garlic cream"
else:
    sauce = "marinara"
#The cheese choice will dictate a few more options. Define the variable first.                
cheese_choice = str(input("Would you like cheese on your pizza? Type y for yes and n for no. "))

请注意,在代码片段中,我们首先定义了尺寸和酱料。我在这里重申,有其他处理这个特定逻辑过程的方法。例如,我们可以将一些变量保存到字典中,并使用数组。目前,我们正在使用到目前为止学到的知识来创建我们的算法,但是在本书的后面,我们将有机会了解其他方法。

前面的代码片段有最终的奶酪选择。无论选择哪个选项,我们都需要对配料做出决定。这将需要发生两次,因为我们需要为“是”和“否”都需要。

看一下以下代码片段,这是前面代码的延续:

ch6_Pizzeria.py

#Toppings need to happen whether or not you want cheese. 
if cheese_choice == "y":
    cheese2_choice = str(input("Would you like regular cheese or extra cheese? Type r for regular and e for extra cheese. "))
    if cheese2_choice == "r":
        cheese = "regular cheese"
    else:
        cheese = "extra cheese"
    toppings1_input = str(input("Would you like mushrooms on your pizza? Type y for yes and n for no. "))
    if toppings1_input == "y":
        toppings1 = "mushrooms"
    else:
        toppings1 = "no mushrooms"
else:
    cheese = "no cheese"    
if cheese_choice == "n":
    toppings1_input = str(input("Would you like mushrooms on your pizza? Type y for yes and n for no. "))
    if toppings1_input == "y":
        toppings1 = "mushrooms"
    else:
        toppings1 = "no mushrooms"
print("You want a " + size_choice + " pizza with " + sauce + " sauce, " + cheese + ", and " + toppings1 + ".")

正如您从代码片段中看到的,我们只使用了蘑菇。在选择家庭尺寸、大蒜酱、普通奶酪和蘑菇后,这个特定代码的输出如下:

Is this a personal or family pizza? Type personal or family. family
Which sauce would you like? Marinara or garlic cream? Type m for marinara and g for garlic cream. g
Would you like cheese on your pizza? Type y for yes and n for no. y
Would you like regular cheese or extra cheese? Type r for regular and e for extra cheese. r
Would you like mushrooms on your pizza? Type y for yes and n for no. y
You want a family pizza with garlic cream sauce, regular cheese, and mushrooms.

使用提供的代码并查看输出,尝试组合其余四种配料的代码。我猜,如果您正在制作自己的比萨饼,欢迎您更改这里提供的选项。只需将橄榄留给自己。

现在,如前所述,我们可能需要返回并进行更改。让我们看一段为您做到这一点的代码片段:

ch6_Pizzeria2.py

ready_end = str(input("Do you need to make any changes? Type y for yes and n for no. "))
if ready_end == "y":
    size_choice = str(input("Is this a personal or family pizza? Type personal or family. "))
    sauce_choice = str(input("Which sauce would you like? Marinara or garlic cream? Type m for marinara and g for garlic cream. "))
    if sauce_choice == "g":
        sauce = "garlic cream"
    else:
        sauce = "marinara"

    cheese_choice = str(input("Would you like cheese on your pizza? Type y for yes and n for no. "))

从代码片段中可以看出,需要对所需的更改进行决策。如果是,则再次提出问题。如果不是,则为用户打印选项。查看完整运行程序的以下输出:

Is this a personal or family pizza? Type personal or family. family
Which sauce would you like? Marinara or garlic cream? Type m for marinara and g for garlic cream. g
Would you like cheese on your pizza? Type y for yes and n for no. n
Would you like mushrooms on your pizza? Type y for yes and n for no. y
Do you need to make any changes? Type y for yes and n for no. y
Is this a personal or family pizza? Type 1 for personal and 2 for family. family
Which sauce would you like? Marinara or garlic cream? Type m for marinara and g for garlic cream. m
Would you like cheese on your pizza? Type y for yes and n for no. n
Would you like mushrooms on your pizza? Type y for yes and n for no. y
You want a family pizza with marinara sauce, no cheese, and mushrooms.

如代码所示,问题被问了两次,因为我们在选项中做了更改。根据您想要问这个问题的频率,您需要继续重复部分代码。有简化的方法,我们将在 Python 语言程序章节(第八章,Python 简介)和后续章节中更深入地讨论这些选项。

在我们继续之前,让我们再看一个问题,重新经历一遍设计过程。

问题 3 - 延迟和 Python

我在 Python 中遇到的第一个问题之一是创建一个根据所选颜色而有不同反应的算法。这类似于如果您正在创建交通灯时会遇到的情况。每个灯的延迟都不同。所以让我们创建一个解决这个问题的算法。我们将使其成为用户选择的颜色,介于绿色、黄色和红色之间,只是为了保持交通灯的主题。因此,让我们做一些假设:

  • 绿色将意味着 5 秒的延迟

  • 黄色将意味着 2 秒的延迟

  • 红色将意味着 4 秒的延迟

这些特定延迟没有特定的原因;我只是想让它们都在 5 秒以内。现在,假设我们正在玩一个游戏,用户必须选择一种颜色。如果他们选择黄色或红色,他们将会有延迟,然后会再次被问及。目标是从程序中获得“您赢了!您现在可以走了”的消息。因此,让我们为此创建一个流程图:

图 6.6 - 交通灯游戏的流程图

图 6.6 - 交通灯游戏的流程图

从流程图中可以看出,如果选择黄色或红色,游戏将重新开始。现在我们已经了解了游戏的基本情况,我们需要编写代码。

重要提示:

为了能够使用延迟,我们需要导入time库。使用代码import time来做到这一点。要包含延迟,我们使用代码time.sleep()

让我们看一段代码片段:

ch6_sleep.py

import time
print("Let's play a game. Choose a color to learn your destiny. Choose wisely or you'll have to start over. ")
i = 0
while i < 4:
    color = str(input("Choose a color: red, green, or yellow. "))
    if color == "green":
        print("You must wait 5 seconds to learn your fate.")
        time.sleep(5)
        print("You win! Excellent choice!")
        break
    elif color == "yellow":
        print("You must wait 2 seconds to learn your fate.")
        time.sleep(2)
        print("You lose! You must start over.")
        i = i + 1
    else:
        print("You must wait 4 seconds to learn your fate.")
        time.sleep(4)
        print("You lose! You must start over.")
        i = i + 1

如您所见,该算法包含了我们在之前章节中讨论循环、布尔语句等时看到的一些代码。如果用户没有赢得游戏,这段特定代码将在三轮后返回到开头。我们使用if-elif-else语句来处理颜色情况。游戏进行三轮的输出如下:

Let's play a game. Choose a color to learn your destiny. Choose wisely or you'll have to start over. 
Choose a color: red, green, or yellow. yellow
You must wait 2 seconds to learn your fate.
You lose! You must start over.
Choose a color: red, green, or yellow. red
You must wait 4 seconds to learn your fate.
You lose! You must start over.
Choose a color: red, green, or yellow. green
You must wait 5 seconds to learn your fate.
You win! Excellent choice!

从游戏输出中可以看出,所有三轮都已经玩过了。每个延迟都按照陈述发生,您需要自己测试,因为我无法用文本显示时间延迟。

制作流程图使得创建这个算法比我一开始读问题后立即编写代码更简单。在编写算法之前,习惯于详细阐述您需要的过程非常重要。设计解决方案可能是一个漫长而乏味的过程,但我们在开始时越有条理,我们的算法就会越好。

总结

在本章中,我们讨论了如何设计、绘制和创建解决问题的解决方案。我们讨论了设计思维的非线性过程,以了解如何最好地设计解决方案。设计思维模型是一个五步过程:共情定义构思原型测试。在计算思维过程中使用这个五步过程可以帮助我们避免许多问题和陷阱。

我们还创建了头脑风暴和流程图,以建立我们的算法决策过程来解决问题。

在下一章中,我们将利用我们对算法设计和解决方案设计的知识,来识别解决方案中的挑战并调试程序。

第七章:识别解决方案中的挑战

在本章中,我们将评估算法和图表,同时学习如何避免一些常见错误,并确定是否可以对现有算法进行可能的调整以简化它。我们将根据问题描述评估解决方案,以验证解决方案是否与问题一致。我们将学习如何识别解决方案设计过程中的陷阱。值得注意的是,我们将在本书的第二部分应用 Python 和计算思维,以及本书的第三部分使用计算思维和 Python 进行数据处理、分析和应用中深入探讨本章的内容,进一步深入了解Python编程语言。

要了解调试,让我们提醒自己计算思维过程并不是线性的。即使我们从原始问题出发,有时我们会重新定义问题,或者需要根据算法所针对的人口变化或者我们想要调整算法设计来调整泛化。但有时,我们会在设计并使用算法后解决问题。根据我们的角色,我们将评估算法的错误、需要的更改等等。了解如何找到和分析错误可以帮助我们,无论我们是绝对的 Python 初学者还是在职业生涯中深入研究。

在本章中,您将学习如何识别和修复程序中的错误,以及如何避免算法设计中的陷阱。

在本章中,我们将涵盖以下主题:

  • 识别算法设计中的错误

  • 调试算法

  • 比较解决方案

  • 完善和重新定义解决方案

技术要求

您需要最新版本的 Python 来运行本章中的代码。您可以在此处找到本章中使用的完整源代码:github.com/PacktPublishing/Applied-Computational-Thinking-with-Python/tree/master/Chapter07

识别算法设计中的错误

对于任何编码人员来说,算法中的错误都是生活中的一个事实。熟悉犯错是很重要的。正如在第五章中提到的,探索问题分析,以及第六章中提到的,解决过程和设计,测试算法并经常测试是一个好习惯。等到完成了数百或数千行代码才测试某些东西是灾难的预兆。是的,我曾经在复制一个游戏时根本没有测试。直到我复制了 4585 行代码。我当时很年轻。说实话,我从未找到我犯的错误。我重新开始,并在每个角落都开始测试。第二次成功了,但我浪费了数周时间复制一切(那是从一本书上复制的——当时还没有 GitHub),然后试图找出错误。所以请不要成为我。请测试您的算法。

现在,在进行调试和处理代码之前,让我们先看看在解决问题时可能遇到的错误。

在本节中,我们将重点关注以下两类广泛的错误:语法错误和逻辑错误。

语法错误

有时语法错误被称为解析错误。当我们忘记缩进、添加冒号、为字符串添加引号等等时,就会产生错误。让我们看看以下各种类型的语法错误。

使用冒号

在 Python 中,冒号用于分隔条件、创建循环等等。冒号是一种告诉算法下一步是这个特定代码块的方式。当我们在 Python 中引入冒号时,它会自动缩进我们代码的下一行。但如果我们忘记在需要的地方包括冒号,程序将无法成功运行。让我们看一个语法错误的例子:

for i in range(1, 10)
    print(i)

如果我们运行这段代码,会得到一个错误消息,说invalid syntax。下面的截图显示了当我们尝试运行这个程序时出现的弹出窗口:

图 7.1 - 错误弹出窗口

图 7.1 - 错误弹出窗口

如果我们从 Python shell 中运行这个程序,错误会如何显示:

SyntaxError: invalid syntax

正如你所看到的,Python 程序会在我们的代码中包含错误时提醒我们。

请注意,在代码中range后面缺少一个冒号。现在,看一下以下代码中的修正语法:

ch7_syntaxerror1.py

for i in range(1, 10):
    print(i)

当我们运行修正后的代码时,程序会运行并打印出数字 1 到 9,如下所示:

1
2
3
4
5
6
7
8
9

你可能还记得range函数不包括上限端点。如果我们想打印数字 10,我们的范围需要是range(1, 11)

现在,让我们看一看 Python 中使用的其他标点符号,可能会导致一些错误,即括号、嵌套括号和括号。

使用嵌套括号和括号

除了涉及冒号的语法错误外,还有嵌套括号的错误。我们必须始终检查每个开括号是否有闭括号。对于括号也是如此。让我们看一下下面的代码,其中包含括号错误:

name = str(input('What is your name? ')
print(name)

正如你所看到的,名称定义中有两个开括号,但只有一个闭括号。当我们运行该程序时,Python 会报告一个无效的语法错误。当我们在 Python shell 或解释器中运行该程序时,会发生什么:

SyntaxError: invalid syntax

现在这是没有错误的相同代码,注意我们甚至将str()去掉了,因为它是不需要的,这样简化了我们的代码,同时消除了错误。

ch7_syntaxerror2.py

name = input('What is your name? ')
print(name)

现在当我们运行代码时,程序会要求输入名称,然后打印出来。输出如下所示:

What is your name? Monique
Monique

正如你所看到的,程序现在可以正常运行了。

在[第三章](B15413_03_Final_SK_ePub.xhtml#_idTextAnchor056)理解算法和算法思维中,我们使用字典创建了一个带有每个菜单项定价的菜单。字典包含括号,用于表示字典开始和结束的位置。让我们看一下几行代码:

cars = {
    "Hyundai": "Kona",
    "Honda": "CR-V",
    "Toyota": "Camry"

print(cars)

如果我们看一下程序,字典缺少闭括号},所以我们会得到一个语法错误,就像我们之前的例子一样。以下片段显示了已纠正的程序:

ch7_syntaxerror3.py

cars = {
    "Hyundai": "Kona",
    "Honda": "CR-V",
    "Toyota": "Camry"
    }
print(cars)

正如你所看到的,一旦添加了括号,程序就会运行并打印出以下输出:

{'Hyundai': 'Kona', 'Honda': 'CR-V', 'Toyota': 'Camry'}

字典中的每个条目都打印在一行中,用逗号分隔。在编写算法时,添加print语句是有帮助的,以确保我们没有任何错误。一旦测试过,我通常会删除不必要的打印函数,但在编写长算法并需要测试以避免问题时,它们确实很有用。

在 Python 编写算法时,还有许多其他错误。让我们再看看一些语法错误。

其他语法错误

在更长的程序中,可能会引入许多其他语法错误。例如,如果你看一下我们刚刚使用的字典,忘记逗号也会创建一个语法错误。通常,当我们尝试运行程序时,这些语法错误会很快被识别出来。Python 会突出显示缩进的位置或者括号缺失的地方。语法错误通常很容易识别,但还有许多其他类型的错误。

逻辑错误

在[第四章](B15413_04_Final_SK_ePub.xhtml#_idTextAnchor071)理解逻辑推理中,我们讨论了可能遇到的逻辑错误:

  • 在等式或语句中使用错误的变量

  • 使用错误的运算符来测试条件

  • 在检查条件时使用错误的缩进

现在我们将看一下逻辑中的其他错误,这些错误在 Python 中有特定的调用,以及每个错误代表什么。

逻辑错误也称为运行时错误。下表显示了 Python 中一些内置错误以及它们的表示:

表 7.1 - 异常和原因/描述表表 7.1 - 异常和原因/描述表

表 7.1 - 异常和原因/描述表

如你所见,在 Python 中有许多不同类型的错误被标记为异常。你可以通过运行以下代码获取 Python 异常列表:

ch7_errors.py

print(dir(locals()['__builtins__']))

当我们运行该代码时,输出提供了以下错误值:

['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException', 'BlockingIOError', 'BrokenPipeError', 'BufferError', 'BytesWarning', 'ChildProcessError', 'ConnectionAbortedError', 'ConnectionError', 'ConnectionRefusedError', 'ConnectionResetError', 'DeprecationWarning', 'EOFError', 'Ellipsis', 'EnvironmentError', 'Exception', 'False', 'FileExistsError', 'FileNotFoundError', 'FloatingPointError', 'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError', 'ImportWarning', 'IndentationError', 'IndexError', 'InterruptedError', 'IsADirectoryError', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'ModuleNotFoundError', 'NameError', 'None', 'NotADirectoryError', 'NotImplemented', 'NotImplementedError', 'OSError', 'OverflowError', 'PendingDeprecationWarning', 'PermissionError', 'ProcessLookupError', 'RecursionError', 'ReferenceError', 'ResourceWarning', 'RuntimeError', 'RuntimeWarning', 'StopAsyncIteration', 'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit', 'TabError', 'TimeoutError', 'True', 'TypeError', 'UnboundLocalError', 'UnicodeDecodeError', 'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning', 'ValueError', 'Warning', 'ZeroDivisionError', '__build_class__', '__debug__', '__doc__', '__import__', '__loader__', '__name__', '__package__', '__spec__', 'abs', 'all', 'any', 'ascii', 'bin', 'bool', 'breakpoint', 'bytearray', 'bytes', 'callable', 'chr', 'classmethod', 'compile', 'complex', 'copyright', 'credits', 'delattr', 'dict', 'dir', 'divmod', 'enumerate', 'eval', 'exec', 'exit', 'filter', 'float', 'format', 'frozenset', 'getattr', 'globals', 'hasattr', 'hash', 'help', 'hex', 'id', 'input', 'int', 'isinstance', 'issubclass', 'iter', 'len', 'license', 'list', 'locals', 'map', 'max', 'memoryview', 'min', 'next', 'object', 'oct', 'open', 'ord', 'pow', 'print', 'property', 'quit', 'range', 'repr', 'reversed', 'round', 'set', 'setattr', 'slice', 'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'vars', 'zip']

如前所述,这些是 Python 中的内置异常。有一种方法可以定义我们自己的异常,但在这本书中我们不会涉及到它们。

请注意,这些不是我们在编程时会遇到的唯一错误。我们可能会因为自己在计算中犯的错误而出现错误,就像我们在第四章中讨论的那样,理解逻辑推理。我们可能会在布尔逻辑中引入错误。目标是尽量避免这些错误,使我们的程序能够正常运行。记住,测试你的算法,并经常测试。

现在让我们看一些带有错误的算法,并尝试识别错误,以便我们可以纠正它们。

调试算法

在 Python 中,我们可以使用breakpoint()函数(内置的)运行调试器。我们可以将这段代码引入到我们的程序中,并在我们对代码不确定的地方插入它。添加breakpoint()将检查错误和 bug。当我们运行breakpoint()函数时,我们会得到一个pdb输出,代表Python 调试器。需要注意的是,这个内置函数出现在Python 3.7和更新版本中。Python 3.6和更早版本的先前调试器是pdb.set_trace()

当我们运行调试器时,我们可以使用四个命令:

  • c:继续执行

  • q:退出调试器/执行

  • n:在函数内部执行下一行的步骤

  • s:在这个函数或被调用的函数中执行下一行的步骤

让我们看一下代码,并运行所列出的每个命令:

ch7_debugger.py

number = 5
number2 = 'five'
print(number)
breakpoint()
print(number2)

看看这段代码,你可以看到print(number)后面的breakpoint()命令。代码将正常运行,直到达到breakpoint()命令。在这个阶段,执行停止。如果我们按下c键,那么它将继续运行程序。看看输出是什么样子。

请注意,在代码中有两个斜杠之间有三个点,/…/。这是因为路径可能会因计算机不同而不同。你的路径将包括程序所在的完整路径:

5
> /Users/.../Python/ch7_debugger.py(8)<module>()
-> print(number2)
(Pdb) c
five

如你所见,它继续打印字符串five,因为它只是继续运行程序。现在让我们看看当我们运行q命令时的输出,它会退出程序:

5
> /Users/.../Python/ch7_debugger.py(8)<module>()
-> print(number2)
(Pdb) q
Traceback (most recent call last):
  File "/Users/.../Python/ch7_debugger.py", line 8, in <module>
    print(number2)
  File "/Users/.../Python/ch7_debugger.py", line 8, in <module>
    print(number2)
bdb.BdbQuit

正如你所看到的,一旦我们使用q命令,由于程序退出,我们会得到一个回溯错误。它打印了breakpoint()代码上面的行,但没有打印第二个print(number2)命令。现在,让我们看看当我们输入n时会发生什么,它应该会带我们到下一行:

5
> /Users/.../Python/ch7_debugger.py(8)<module>()
-> print(number2)
(Pdb) n
five
--Return--
> /Users/.../Python/ch7_debugger.py(8)<module>()->None
-> print(number2)
(Pdb)

如你所见,当我们输入n时,程序继续运行并打印第二个命令行。当这样做时,你可以看到-> None输出和运行的代码:print(number2)。最后,让我们看一下稍微改变的代码,看看在运行调试器时使用s会发生什么:

ch7_debugger2.py

number = 5
number2 = 'five'
print(number)
breakpoint()
print(str(number) + number2)

当我们运行这个程序和调试器时,如果我们使用s,我们会得到以下输出:

5
> /Users/.../Python/ch7_debugger2.py(8)<module>()
-> print(number + " " + number2)
(Pdb) s
TypeError: unsupported operand type(s) for +: 'int' and 'str'
> /Users/.../Python/ch7_debugger2.py(8)<module>()
-> print(number + " " + number2)
(Pdb)

如你所见,程序遇到了TypeError并提供了更多信息。我尝试将整数和字符串组合在一起。因此,我们需要修复代码以正确运行。在我这样做之前,让我们看看当我尝试使用c继续代码时会发生什么:

5
> /Users/.../Python/ch7_debugger2.py(8)<module>()
-> print(number + " " + number2)
(Pdb) c
Traceback (most recent call last):
  File "/Users/.../Python/ch7_debugger2.py", line 8, in <module>
    print(number + " " + number2)
TypeError: unsupported operand type(s) for +: 'int' and 'str'

如你所见,我可以从两个命令中获得相同的信息,程序的响应略有不同。要解决这个问题,我必须将数字转换为字符串,在print行中可以使用以下代码来实现:

ch7_debugger3.py

number = 5
number2 = 'five'
print(number)
breakpoint()
print(str(number) + " " + number2)

现在,我已经修复了代码,使得打印行中的项目都是字符串,当我使用c继续时,输出如下:

5
> /Users/.../Python/ch7_debugger3.py(8)<module>()
-> print(str(number) + " " + number2)
(Pdb) c
5 five 

如你所见,程序现在打印了正确的信息,将数字作为字符串与five字符串组合在一起。双引号在它们之间添加了一个空格,我们以前已经见过,但当我们在第八章中查看 Python 基础知识时,将再次讨论。

现在,让我们看一下相同问题的一些解决方案,以便分析它们。

比较解决方案

当我们看问题时,我已经提到在 Python 中有多种方法可以做同样的事情。根据我们试图实现的目标,一些命令可能比我们的算法中的其他命令更好。让我们首先看一下一个问题的几种解决方案。

问题 1 - 打印偶数

你被要求编写一个算法,根据用户提供的范围打印偶数。也就是说,如果用户输入范围为 2 到 20,那么程序将打印 2, 4, 6, 8, 10, 12, 14, 16, 18 和 20。让我们假设如果端点是偶数,我们希望包括端点。

让我们看一下两种可能解决方案中的第一种。记住,一个解决方案可能不比另一个更好。很大程度上取决于你的完整算法的目标。列表更合适吗?字典?函数?当我们设计解决方案时,这些问题很重要。

算法解决方案 1 - 打印偶数

回想一下,我们将接受用户输入来创建一个给定范围内的偶数列表。看一下以下代码,它要求用户输入,然后打印出数字:

ch7_evenalgorithm1.py

print("This program will print the even numbers for any range of numbers provided.")
endpoint1 = int(input("What is the lower endpoint of your range? "))
endpoint2 = int(input("What is the upper endpoint of your range? "))
endpoint2 = endpoint2 + 1
for i in range(endpoint1, endpoint2):
    if i % 2 == 0:
        print(i)

注意,endpoint2被转换为endpoint2 + 1。这是因为如果我们不添加1,那么如果它是一个偶数,上限端点将不会被包括在内。程序还以用户的打印消息开始,说明程序的功能是什么。

当我用端点26运行这个程序时,我得到以下输出:

This program will print the even numbers for any range of numbers provided.
What is the lower endpoint of your range? 2
What is the upper endpoint of your range? 6
2
4
6

如你所见,两个端点都是偶数且包括在内。如果我们用端点39运行程序,我们得到以下输出:

This program will print the even numbers for any range of numbers provided.
What is the lower endpoint of your range? 3
What is the upper endpoint of your range? 9
4
6
8

尽管终点现在在技术上是10,但范围的上限并不包括在内,因此在10以下的最大偶数是8。现在,我可以为一个更大的范围运行这个程序,但是范围越大,滚动以获取所有数字就越困难。因此,让我们看一种不同的方法来获取我们的偶数。

算法解决方案 2 - 打印偶数

正如我们从前面的例子中看到的,每个偶数都被打印到不同的行。让我们看看是否可以改变这一点,而是创建一个列表。Python 中的列表可以是空的。我们可以为它们使用任何名称,然后将它们等于括号内的项目或只是空括号。

例如,我可以创建一个名为evenNumbers = []的空列表。让我们看看以下算法中的情况:

ch7_evenalgorithm2.py

print("This program will print the even numbers for any range of numbers provided.")
endpoint1 = int(input("What is the lower endpoint of your range? "))
endpoint2 = int(input("What is the upper endpoint of your range? "))
endpoint2 = endpoint2 + 1
evenNumbers = []
for i in range(endpoint1, endpoint2):
    if i % 2 == 0:
        evenNumbers.append(i)

print(evenNumbers)

你可以看到代码的前几行是相同的。在这个特定的代码中唯一的区别是数字的打印方式。列表是在for循环之前创建的。然后,使用evenNumbers.append(i)代码将每个数字附加到列表中。最后,我们打印我们的列表以获得以下输出:

This program will print the even numbers for any range of numbers provided.
What is the lower endpoint of your range? 2
What is the upper endpoint of your range? 10
[2, 4, 6, 8, 10]

如你所见,所有偶数都包含在一个列表中,这比一个接一个地打印更容易阅读。想象一下,如果你必须打印范围在 300-1,000 之间的偶数。当我们运行程序时,列表会使阅读更容易。对于第二个算法,输出如下:

This program will print the even numbers for any range of numbers provided.
What is the lower endpoint of your range? 300
What is the upper endpoint of your range? 1000
[300, 302, 304, 306, 308, 310, 312, 314, 316, 318, 320, 322, 324, 326, 328, 330, 332, 334, 336, 338, 340, 342, 344, 346, 348, 350, 352, 354, 356, 358, 360, 362, 364, 366, 368, 370, 372, 374, 376, 378, 380, 382, 384, 386, 388, 390, 392, 394, 396, 398, 400, 402, 404, 406, 408, 410, 412, 414, 416, 418, 420, 422, 424, 426, 428, 430, 432, 434, 436, 438, 440, 442, 444, 446, 448, 450, 452, 454, 456, 458, 460, 462, 464, 466, 468, 470, 472, 474, 476, 478, 480, 482, 484, 486, 488, 490, 492, 494, 496, 498, 500, 502, 504, 506, 508, 510, 512, 514, 516, 518, 520, 522, 524, 526, 528, 530, 532, 534, 536, 538, 540, 542, 544, 546, 548, 550, 552, 554, 556, 558, 560, 562, 564, 566, 568, 570, 572, 574, 576, 578, 580, 582, 584, 586, 588, 590, 592, 594, 596, 598, 600, 602, 604, 606, 608, 610, 612, 614, 616, 618, 620, 622, 624, 626, 628, 630, 632, 634, 636, 638, 640, 642, 644, 646, 648, 650, 652, 654, 656, 658, 660, 662, 664, 666, 668, 670, 672, 674, 676, 678, 680, 682, 684, 686, 688, 690, 692, 694, 696, 698, 700, 702, 704, 706, 708, 710, 712, 714, 716, 718, 720, 722, 724, 726, 728, 730, 732, 734, 736, 738, 740, 742, 744, 746, 748, 750, 752, 754, 756, 758, 760, 762, 764, 766, 768, 770, 772, 774, 776, 778, 780, 782, 784, 786, 788, 790, 792, 794, 796, 798, 800, 802, 804, 806, 808, 810, 812, 814, 816, 818, 820, 822, 824, 826, 828, 830, 832, 834, 836, 838, 840, 842, 844, 846, 848, 850, 852, 854, 856, 858, 860, 862, 864, 866, 868, 870, 872, 874, 876, 878, 880, 882, 884, 886, 888, 890, 892, 894, 896, 898, 900, 902, 904, 906, 908, 910, 912, 914, 916, 918, 920, 922, 924, 926, 928, 930, 932, 934, 936, 938, 940, 942, 944, 946, 948, 950, 952, 954, 956, 958, 960, 962, 964, 966, 968, 970, 972, 974, 976, 978, 980, 982, 984, 986, 988, 990, 992, 994, 996, 998, 1000]

我之所以只打印这一个而不是第一个算法,是因为第一个算法需要很多页,我们不想在这本书中浪费纸张。你可以看到其中一个比另一个更容易使用和更合适,因为更容易阅读更大的数字组。

这就是为什么我们需要审视我们所有的算法,并确定它们是否是表达我们所需的最佳方式。虽然有些算法可以工作,但它们可能不是最佳解决方案,有时这是可以接受的。但有时,进行一些更改,有时甚至是添加几行代码,就像我们在算法 2中所做的那样,可以显著改变我们的输出,并对我们更有帮助。

当我们比较这两个算法时,我们也在不断完善和重新定义我们的解决方案,这在下一节中我们会做更多。

精炼和重新定义解决方案

如果我们长时间观察算法,总是可以找到方法来完善和重新定义它们。想想我们手机上的应用有多少更新。总有人在玩弄这些应用,使它们更安全,增加游戏的关卡,更新艺术文件等等。作为程序员/编码人员,我们总是在努力让我们的工作变得更好。

我们将从一个算法开始这一节。以下程序打印出三只宠物的名字:

ch7_pets1.py

cat = "Whiskers"
dog = "King Kong"
bird = "Pirate"
print("The cat's name is " + cat + ", the dog's name is " + dog + \
      ", and the bird's name is " + bird + ".")

这个简单的代码中包含了一切,所以这次没有用户输入。你可以看到在print()命令中的dog +后使用了\字符。这个反斜杠允许我们在下一行添加剩余的代码,这样我们可以更容易地阅读它。

代码的输出如下:

The cat's name is Whiskers, the dog's name is King Kong, and the bird's name is Pirate.

正如你所看到的,这是一个简单的句子,带有宠物名字。

现在,假设我们有一只猫、一只狗和一只鸟,但它们的名字不一样。我们可以使用一个带有三个参数的函数。请记住,我们将在第八章中详细介绍函数的定义和信息,Python 简介。现在,让我们看看带有函数的算法。我们将函数命名为myPets()。看看以下算法:

ch7_pets2.py

def myPets(cat, dog, bird):
    print("The cat's name is " + cat + ", the dog's name is " + dog +\
          ", and the bird's name is " + bird + ".")
myPets(cat = "Whiskers", dog = "King Kong", bird = "Pirate")

这个算法看起来与上一个非常相似,只是名字的定义在代码的最后一行。调用函数时,使用该行的信息来填写上面算法行中的定义的空白。输出看起来与上一个代码相同:

The cat's name is Whiskers, the dog's name is King Kong, and the bird's name is Pirate.

现在,正如你所看到的,这只打印了一个函数,因为我们只提供了一个信息,但我们可以随时调用函数,使用任意数量的值。看看这个算法:

ch7_pets3.py

def myPets(cat, dog, bird):
    print("The cat's name is " + cat + ", the dog's name is " + dog +\
          ", and the bird's name is " + bird + ".")
myPets(cat = "Whiskers", dog = "King Kong", bird = "Pirate")
myPets(cat = "Mimi", dog = "Jack", bird = "Peyo")
myPets(cat = "Softy", dog = "Leila", bird = "Oliver")

正如你所看到的,现在函数将被调用三次。我们只有一个print()命令,但是函数定义意味着每次调用函数时都会使用print()命令。看看输出是什么样子的:

The cat's name is Whiskers, the dog's name is King Kong, and the bird's name is Pirate.
The cat's name is Mimi, the dog's name is Jack, and the bird's name is Peyo.
The cat's name is Softy, the dog's name is Leila, and the bird's name is Oliver.

请注意,当我们调用函数时,使用了三组宠物名字,打印了三个不同的句子。

当我们编写算法时,重要的是要考虑我们现在需要什么,以及我们将来可能需要什么。对于一个实例来说,使用第一个算法是可以的,但是如果我们想要为社区中的每个人或者教室中的每个学生运行算法,那么第二个算法更有帮助。重新定义我们的需求并完善我们的算法有助于改进我们从程序中得到的东西。

请注意,正如前面提到的,我们将在第八章中更多地讨论函数,Python 简介。我们将讨论的其中一件事是为未知数量的参数创建一个函数。例如,如果我只有一只狗和一只鸟呢?我们可以通过对算法进行一些更改来解决这个问题。我们很快就会研究这个问题。目前,我们只是稍微了解了为什么有时需要比较算法并重新定义和重新设计它们以更好地满足我们的需求。

摘要

在本章中,我们讨论了算法设计中的错误以及如何调试解决方案。我们还学习了如何比较解决方案,并在需要时对解决方案进行改进和重新设计。阅读完本章后,您应该更了解算法中的语法错误以及如何在Python 3.7及以上版本中使用breakpoint()命令来使用调试器。内置调试器为您提供了四种操作:c = 继续q = 退出n = 下一行s = 步进

使用调试器可以帮助我们确定代码中可能出错的地方。我们可以在代码的任何位置添加这行代码来确定问题所在。

我们还研究了提供相同输出但使用不同代码的算法。通过比较算法解决方案,我们可以确定哪些更有用,哪些更适合我们的问题或情况,以及为什么我们应该选择其中一个而不是另一个。请记住,算法是指令列表。在广泛使用算法的情况下,知道使用哪些指令至关重要。某些解决方案可能比其他解决方案更适合您的问题。考虑算法的目的、算法中的代码片段以及它们将如何在更大的算法中使用,并相应地做出决定。每个问题和每个解决方案都是独一无二的。

当我们完成本书的第一部分计算思维导论时,我们已经了解了计算思维过程,始终关注可能的场景,以帮助我们理解该过程的有用性,如何进行头脑风暴并为决策创建流程图,以及如何设计我们的算法。在第二部分应用 Python 和计算思维中,我们将更深入地研究 Python 语言,以便能够解决更复杂的问题,比如处理数据和函数的问题。我们还将更深入地了解 Python 编程语言,并将在后续章节中将这些知识应用于多种类型的问题。

第二部分:应用 Python 和计算思维

在选择编程语言时有多种选择。Python 编程语言是一种强大的开源面向对象的编程语言。它是一种高级编程语言,拥有越来越多的库和包,不断扩展其功能。我们可以使用 Python 来编写从简单的数学算法到复杂的数据科学算法,甚至机器学习的程序。了解 Python 也意味着了解计算思维的多个领域。

在本节中,您将学习如何在使用 Python 编程语言解决问题时使用计算思维过程。

本节包括以下章节:

  • 第八章,Python 简介

  • 第九章,理解输入和输出以设计解决方案算法

  • 第十章,控制流

  • 第十一章,使用计算思维和 Python 解决简单挑战

第八章:Python 简介

在本章中,我们将学习 Python 的命令和功能,并将它们应用到问题中。当我们深入第二部分应用 Python 和计算思维的第一章时,我们将使用更复杂的 Python 编程。在本章中,我们将更多地关注语言,而其余章节将侧重于应用。

在本章中,我们将涵盖以下主题:

  • 介绍 Python

  • 使用字典和列表

  • 使用变量和函数

  • 学习文件、数据和迭代

  • 使用面向对象的编程

当我们深入研究 Python 编程语言时,请记住,一些内容已经在之前的章节中涵盖过,比如我们在研究计算思维过程时使用的字典和函数。本章将帮助您更轻松地找到满足您计算思维问题需求的 Python 命令的关键信息。

技术要求

您需要最新版本的 Python 来运行本章中的代码。您可以在此处找到本章中使用的完整源代码:github.com/PacktPublishing/Applied-Computational-Thinking-with-Python/tree/master/Chapter08

介绍 Python

由于其易用性,Python 是增长最快的编程语言之一。Python 的吸引力之一是,我们可以用更少的代码行和更简单的语言和语法编写与 C、C++和 Java 等语言相同的程序。Python 的另一个吸引力在于它是可扩展的,这意味着我们可以为其添加功能和功能。

虽然并非所有功能都是内置的,但我们可以使用库来添加我们需要的功能。这些库可以下载并使用。例如,如果我们想要处理数据和数据科学,我们可以下载一些库,比如PandasNumPyMatplotlibSciPyScikit Learn等。但在我们深入研究这些库之前,让我们先了解一下 Python 语言的工作原理并学习其基础知识。

Python 具有一些内置的引用函数。下表按字母顺序列出了这些函数:

表 8.1 - Python 内置函数

表 8.1 - Python 内置函数

虽然我们不会在本书中涵盖所有的函数,但在研究 Python 及其算法时,我们会使用其中一些函数。让我们从这里列出的一些数学函数开始。

数学内置函数

在 Python 中,一些数学函数已经内置,比如abs()eval()max()min()sum()函数。这些并非所有内置的数学函数,但我们将仔细研究这些特定函数,以了解 Python 如何处理它们。

abs()函数将帮助我们找到一个数的绝对值,无论是整数还是浮点数。请看下面的代码片段:

ch8_absFunction.py

x = abs(-3.89)
print(x)

当我们运行这个程序时,我们将得到–3.89的绝对值。请记住,一个数的绝对值是它到 0 的距离。运行这个程序时,请看一下输出:

3.89

由于绝对值始终是正数,当我们运行abs(–3.89)时,我们得到3.89

另一个有用的数学函数是eval()函数。我们可以在这个函数中定义一个变量,然后调用 Python 来使用该值评估代数表达式。在 Python shell 中,我们可以首先定义变量如下:

>>> p = 2

现在变量已经定义,我们可以用任何表达式调用eval()函数。例如,请看下面的输入和输出:

>>> eval('2 * p - 1')

如您所见,Python 使用了先前定义的p2,并替换然后评估表达式以产生这个输出:

3

Python 程序也可以作为计算器使用,因此你可以像平常一样进行数学运算。以下是一些例子:

>>> 10-8

如你所见,Python 知道将破折号视为减法,并产生数学表达式的结果:

2

在下一个案例中,Python 将+解释为数学符号,并提供了求和表达式的结果:

>>> 4+5

输出是:

9

注意最后一个表达式10**5。在 Python 中,我们可以使用两个星号(**)表示指数:

>>> 10**5

输出是:

100000

现在,让我们看看max()函数。这个函数可以用在可迭代的列表上,但我们可以用只有两个数字的列表来测试它:

>>> max(12, 30)

你可以清楚地看到输出是什么:

30

让我们看另一个例子:

>>> max(100, 10)

这是输出:

100

从输出中可以看出,该函数总是选择最大的项。你可以添加第三个项,该函数仍然会选择最大值。这些内置函数在某种程度上很聪明,它们被编码为能够处理所提供的内容 - 例如两个或三个项 - 而无需明确告诉 Python 我们将引入多少项:

>>> max(230, 812, 109)

获得的输出是:

812

如你所见,我们不必向内置函数添加任何内容来找到三个值的最大值。

min()函数则相反;它选择最小值。看一下下面的最小函数:

>>> min(230, 812, 109)

这是输出:

109

如你所见,该函数使用了与最大函数相同的列表,但这次输出是109,这是该组数字的最小值。

还有其他数学函数,比如sum()。如果你是 Python 初学者,建议你尝试使用这些函数来了解它们的工作方式。这些函数将成为你算法的关键,因为你设计计算思维问题的解决方案。当我们研究其他 Python 功能时,比如字典和数组,我们也会使用其中一些函数。

使用字典和列表

在深入研究字典和列表之前,重要的是要注意 Python 的内置函数中不包含数组。我们可以使用列表,并对列表执行许多传统的数组函数。但是,对于数组的更强大功能,需要使用库,例如 NumPy。

Python 有四种集合数据类型,如下所示:

  • 列表:有序且可更改;可以有重复项

  • 元组:有序且不可更改;可以有重复项

  • 集合:无序且无索引;不能有重复项

  • 字典:无序、可更改和有索引;不能有重复项

如前所述,我们暂时不会涉及 NumPy 库或其他数据库。现在,我们将专注于字典和列表。

定义和使用字典

你可能还记得我们在第三章中使用了字典,理解算法和算法思维,当时我们创建了一个项目菜单。Python 中的字典是具有以下三个特征的集合:

  • 它们是无序的。

  • 它们是可更改的。

  • 它们由键索引。

字典是以值对的形式组织的。例如,我们可以有一个包含州和它们的首府值对的字典。看一下下面的字典:

ch8_dictionary1.py

states = {
    'Ohio':'Columbus',
    'Delaware':'Dover',
    'Pennsylvania':'Harrisburg',
    'Vermont':'Montpelier'
    }
print(states)

如果我们在没有条件的情况下将这个字典打印出来,我们会得到以下输出:

{'Ohio': 'Columbus', 'Delaware': 'Dover', 'Pennsylvania': 'Harrisburg', 'Vermont': 'Montpelier'}

正如你所看到的,每个值对都一次打印出来。就像我们以这种方式构建了字典一样,Python 也有一个内置的dict()函数。可以使用该函数构建相同的字典,如下所示:

ch8_dictionary2.py

states = dict([
    ('Ohio','Columbus'),
    ('Delaware','Dover'),
    ('Pennsylvania','Harrisburg'),
    ('Vermont','Montpelier')
])
print(states)

你可以看到,这两个例子中字典的构造方式非常相似,语法上有一些变化,比如第一个实例中使用冒号,而第二个实例中使用括号和逗号。然而,print语句产生了相同的结果。

据说字典是键值对,因为第一个项目是键,第二个配对项目是值。因此,对于OhioColumbusOhio,而Columbus。我们可以使用键来调用任何值。例如,我可以调用states['Ohio'],它应该返回'Columbus'。看一下代码:

>>> states['Ohio']

这是输出:

'Columbus'

我们可以为字典中的任何键值对做到这一点。但是,如果我们尝试调用字典中没有的键,比如Alabama,那么我们会得到以下错误:

>>> states['Alabama']

这导致显示以下错误:

Traceback (most recent call last):
  File "<pyshell#23>", line 1, in <module>
    states['Alabama']
KeyError: 'Alabama'

请注意,这会导致KeyError,在第七章的错误列表中提到,在解决方案中识别挑战,在逻辑错误部分。但是假设我们确实想要添加Alabama和首都Montgomery。我们可以使用以下代码来实现:

>>> states['Alabama'] = 'Montgomery'

我们可以在输入以下代码后再次调用字典:

>>> print(states)

这给了我们以下输出:

{'Ohio': 'Columbus', 'Delaware': 'Dover', 'Pennsylvania': 'Harrisburg', 'Vermont': 'Montpelier', 'Alabama': 'Montgomery'}

请注意,字典在字典末尾添加了'Alabama':'Montgomery'键值对。

我们还可以使用del代码删除键值对,如下所示:

>>> del states['Delaware']

现在,如果我们继续打印states字典,Delaware就不再在列表中了:

>>> print(states)

删除州之后的输出:

{'Ohio': 'Columbus', 'Pennsylvania': 'Harrisburg', 'Vermont': 'Montpelier', 'Alabama': 'Montgomery'}

现在,您可以继续添加项目或删除它们,而无需进入主算法。

使用字典,您还可以向一个键添加多个值。假设我玩三种运动(我没有)。我可以在字典中列出它们,并将它们与一个键值匹配。看一下以下算法:

ch8_dictionary3.py

miscellaneous = {
    'sports' : ['tennis', 'bowling', 'golf'],
    'age' : '40',
    'home' : 'lake'
    }

我们可以使用print(miscellaneous)打印完整的字典,如下所示:

>>> print(miscellaneous)

这就是我们得到输出的方式:

{'sports': ['tennis', 'bowling', 'golf'], 'age': '40', 'home': 'lake'}

如您所见,打印的字典包括所有值。如果我只想打印运动项目,那么我可以使用miscellaneous['sports']代码:

>>> miscellaneous['sports']

这是输出:

['tennis', 'bowling', 'golf']

请注意,我们根本没有使用print()函数。我们使用了我们称之为miscellaneous的字典,并调用了'sports'键。请注意,我们之所以能够得到这个结果,是因为我们在 Python 的IDLE命令窗口中调用了字典。如果您想将其包含在您的算法中,您仍然需要使用print()函数。

虽然我们在这里不会详细介绍字典的所有功能,但您可以看到它们如何有助于创建包含键值对的算法。Python 允许我们添加和修改字典,调用值等,而无需访问整个字典来实现这一点。

接下来,让我们来看看列表。

定义和使用列表

Python 中的列表是有序且可更改的。我们可以为任何事物创建列表,比如动物的类型、颜色、水果、数字,或者真的任何我们想要的东西。由于我们可以有重复的值,我们可以有一个只说apple, apple, apple的三个苹果的列表。例如,看一下所示的列表:

ch8_list1.py

fruits = ['apple','apple','apple']
print(fruits)

当我们打印这个列表时,所有项目都包括在内。输出如下:

['apple', 'apple', 'apple']

正如您所看到的,我们有相同的值三次。我们无法在字典中做到这一点,因为字典不允许重复成员。

现在,让我们看看我们可以用列表做什么。让我们从以下动物列表开始:

ch8_list2.py

animals = ['dog', 'cat', 'bird', 'lion', 'tiger', 'elephant']

列表中的第一项是'dog'。列表有索引,从 0 开始。因此,如果我们打印animals[0],我们会得到dog。我们可以在这里检查:

>>> print(animals[0])

这是索引 0 的输出:

dog

列表中有六个项目,但最后一个索引是[5]。因此,要打印elephant,我们需要使用该索引进行打印:

>>> print(animals[5])
elephant

您还可以在列表中使用负数索引。[–1]索引是指最后一个索引,因此它代表elephant,与索引[5]相同:

>>> print(animals[-1])
elephant

[–2]索引是指倒数第二个索引,因此是tiger,以此类推。

我们还可以通过指定索引范围来打印列表中的多个项目。看一下以下代码:

>>> print(animals[1:4])

这是输出:

['cat', 'bird', 'lion']

如你所见,我们打印了列表中的第二个项目,对应索引[1],以及接下来的两个项目。

使用列表,我们还可以添加项目,替换项目,删除项目,检查长度等等。要添加一个项目,我们需要使用append()方法。让我们把duck项目添加到我们的列表中:

>>> animals.append('duck')
>>> print(animals)

这是添加的项目:

['dog', 'cat', 'bird', 'lion', 'tiger', 'elephant', 'duck']

请注意,我们的列表现在在列表末尾有duck。但是假设我们想要移除bird并用butterfly替换它:

首先,我们必须确定bird的索引。该索引是2,因为它是列表中的第三个项目:

>>> animals[2] = 'butterfly'
>>> print(animals)

这是输出,bird被替换了:

['dog', 'cat', 'butterfly', 'lion', 'tiger', 'elephant', 'duck']

列表现在包含butterfly

删除一个项目相当简单,我们只需使用remove()方法和我们想要删除的项目:

>>> animals.remove('lion')
>>> print(animals)

如你所见,lion已经从列表中移除了:

['dog', 'cat', 'butterfly', 'tiger', 'elephant', 'duck']

我们还可以通过索引使用pop()方法删除一个项目。使用索引1会移除列表中的第二个项目。参考以下代码:

>>> animals.pop(1)
'cat'

然后,我们可以尝试以下操作:

>>> print(animals)
['dog', 'butterfly', 'tiger', 'elephant', 'duck']

请注意,Python 识别了从列表中弹出的索引1处的项目。当我们再次打印列表时,该项目就不再存在了。如果我们想要移除最后一个项目,我们不必指定索引。参考以下代码:

>>> animals.pop()
'duck'

然后,我们可以尝试以下操作:

>>> print(animals)
['dog', 'butterfly', 'tiger', 'elephant']

如前所述,当没有指定索引时,列表上的最后一个项目会被弹出并从列表中移除。

还有一种方法可以从列表中删除一个项目,那就是使用del关键字:

>>> del animals[1]
>>> print(animals)

这是输出:

['dog', 'tiger', 'elephant']

我们的列表失去了列表中的第二个项目。我们还可以使用del关键字完全删除列表:

>>> del animals
>>> print(animals)

这个错误是输出在这里:

Traceback (most recent call last):
  File "<pyshell#49>", line 1, in <module>
    print(animals)
NameError: name 'animals' is not defined

如你所见,我不能再打印列表,因为它没有定义。请注意,我们收到了一个NameError描述name 'animals' is not defined。这是第七章中提到的另一个错误,在解决方案中识别挑战

重要提示:

在接下来的几个代码示例中,我再次运行了ch8_list2.py文件中的原始代码,以获得我的原始列表。

我们可以使用clear()方法清空整个列表,而不是消除实际的列表。

>>> animals.clear()
>>> print(animals)

这是我们得到的输出:

[]

在这种情况下,列表现在打印为空列表,而不会给出错误消息。这是因为列表仍然存在并且已定义,只是空的。

现在,假设我想要知道我的列表的长度。我们可以使用len()来找到列表的长度。同样,我回到原始列表运行以下代码:

>>> print(len(animals))

我们得到以下输出:

6

我们的原始列表包含六个元素 - 也就是六种动物。

现在,让我们定义另一个包含颜色的列表:

ch8_list3.py

animals = ['dog', 'cat', 'bird', 'lion', 'tiger', 'elephant']
colors = ['blue', 'red', 'yellow']
print(animals)
print(colors)

这个算法的输出是两个列表。现在,如果我们想要合并这两个列表,我们可以使用extend()方法。

让我们看看如何将colors附加到animals列表中:

>>> animals.extend(colors)
>>> print(animals)

这是追加后的列表:

['dog', 'cat', 'bird', 'lion', 'tiger', 'elephant', 'blue', 'red', 'yellow']

我们的列表现在包含了所有的动物和所有的颜色。我们也可以使用以下方法将colorsanimals扩展。不同之处在于颜色会首先出现在我们的列表中:

>>> colors.extend(animals)
>>> print(colors)

现在列表的样子是这样的:

['blue', 'red', 'yellow', 'dog', 'cat', 'bird', 'lion', 'tiger', 'elephant']

我们还可以对列表进行排序,当我们想要有一个按字母顺序排列的列表或者想要对数字列表进行排序时,这是很有帮助的。看一下以下算法中的两个列表:

ch8_list4.py

animals = ['dog', 'cat', 'bird', 'lion', 'tiger', 'elephant']
numbers = [4, 1, 5, 8, 2, 4]
print(animals)
print(numbers)

这是它们未排序时的样子:

['dog', 'cat', 'bird', 'lion', 'tiger', 'elephant']
[4, 1, 5, 8, 2, 4]

让我们对两个列表进行排序,看看会发生什么:

>>> numbers.sort()
>>> print(numbers)

我们将得到以下输出:

[1, 2, 4, 4, 5, 8]

然后,我们可以尝试以下操作:

>>> animals.sort()
>>> print(animals)

我们得到了这个输出:

['bird', 'cat', 'dog', 'elephant', 'lion', 'tiger']

正如你所看到的,数字按从小到大的顺序排序,而动物按字母顺序排序。

让我们谈一下为什么这样很有帮助。想象一下,你的网站上显示了来自 Python 列表的项目列表。它们都被排序并完美地显示出来。但现在让我们说你想添加更多的项目。使用我们讨论过的方法,将它们以任何顺序添加到你的列表中会更容易,然后我们可以对它们进行排序,以便它们继续按字母顺序排列。

当然,这些不是我们可以用列表做的唯一的事情。Python 允许我们以许多用户友好的方式使用列表,并且与其他编程语言中的数组类似地进行操作。当我们需要以其他方式使用它们时,我们可以搜索包含这些功能的库。

在 Python 编程语言中,列表和字典都很重要。在本节中,你看到字典使用键值对,而列表包括值。两者都可以使用内置在 Python 编程语言中的方法和函数进行编辑。当我们将 Python 应用于更复杂的计算思维问题时,你将再次看到它们,这在本书的第三部分使用计算思维和 Python 进行数据处理、分析和应用中。

现在,让我们看看如何在 Python 中使用变量和函数。

使用变量和函数

在 Python 中,我们使用变量来存储一个值。然后我们可以使用这个值来执行操作,评估表达式,或者在函数中使用它们。函数在算法中被调用时给出一系列指令。许多函数在其中包括变量。所以,让我们首先看看如何定义和使用变量,然后再看看 Python 函数。

Python 中的变量

Python 没有声明变量的命令。我们可以通过给它们命名并将它们设置为我们想要的任何值来创建变量。让我们看一个包含多个变量的算法:

ch8_variables.py

name = 'Marcus'
b = 10
country_1 = 'Greece'
print(name)
print(b)
print(country_1)

正如你所看到的,我们可以使用字母、更长的名称,甚至在变量命名中包括下划线。但是,我们不能以数字开头命名变量。当我们运行这个程序时,我们得到以下输出:

Marcus
10
Greece

每个变量都被打印出来,没有任何问题。如果我们使用数字来开始任何变量名,我们会得到一个错误。但是,如果我将country_1变量命名为_country,那将是 Python 中一个可接受的变量名。

现在,让我们看看我们可以用变量做什么。

组合变量

变量允许我们做的一件事是在print语句中组合它们。例如,我可以创建一个打印Marcus Greeceprint语句。为了做到这一点,我可以使用+字符,如下所示:

>>> print(name + ' ' + country_1)

请注意,在两个+字符之间,有' '。这是为了添加一个空格,这样我们的print语句就不会看起来像MarcusGreece。现在,让我们在print语句中组合bname

>>> print(b + name)

这将给我们一个错误消息:

Traceback (most recent call last):
  File "<pyshell#70>", line 1, in <module>
    print(b + name)
TypeError: unsupported operand type(s) for +: 'int' and 'str'

请注意,错误说明了TypeError: unsupported operand types for +。它还说明了我们有'int''str',这意味着我们正在组合两种不同的数据类型。

为了能够在print语句中组合这两种数据类型,我们可以将int(我们的b变量)转换为str。看起来是这样的:

>>> print(str(b) + ' ' + name)

这很容易给我们想要的结果:

10 Marcus

现在我们已经将它们都变成了字符串,我们可以在print函数中将它们组合起来。

有时,我们会想一次创建多个变量。Python 允许我们用一行代码来做到这一点:

ch8_variables2.py

a, b, c, d = 'John', 'Mike', 'Jayden', 'George'
print(a)
print(b)
print(c)
print(d)

当我们运行这个算法时,我们得到以下输出:

John
Mike
Jayden
George

正如你所看到的,当我们打印a变量时,等号右边的第一个值被打印出来。算法的第一行分配了四个不同的值给四个不同的变量。

现在让我们看看函数,因为我们需要它们来进一步讨论变量。

使用函数

第七章在解决方案中识别挑战中,我们编写了一个算法,打印出任何给定数字范围内的偶数。我们将通过定义一个函数来重新访问这个问题。让我们看一下偶数问题的算法:

ch8_evenNumbers.py

def evenNumbers(i, j):
    a = i - 1
    b = j + 1
    for number in range(a, b):
        if number % 2 == 0:
            print(number)
            a = a + 1

如果我们在 Python 中运行此程序,将不会有输出;但是,我们现在可以为任何数字范围运行该函数。请注意,我们在此函数中添加了ab变量。这样在程序运行时就会包括端点。

让我们看看当我们运行范围为(2, 10)(12, 25)时会发生什么:

>>> evenNumbers(2, 10)

输出如下:

2
4
6
8
10

然后,我们可以尝试以下操作:

>>> evenNumbers(12, 25)

输出如下:

12
14
16
18
20
22
24

正如您所看到的,我们无需进入算法来调用函数,一旦我们运行了函数,我们就可以在 Python shell 中为任何范围调用它。与以前一样,如果我们的范围太大,shell 将显示一个非常长的数字列表。因此,我们可以定义另一个变量,这次是一个列表,并在函数内部将值附加到该列表:

ch8_evenNumbers2.py

listEvens = []
def evenNumbers(i, j):
    a = i - 1
    b = j + 1
    for number in range(a, b):
        if number % 2 == 0:
            listEvens.append(number)
            a = a + 1
    print(listEvens)

请注意,我们在函数外定义了列表。我们可以以任何方式做。它可以存在于函数内部或外部。区别在于,即使未调用函数,列表也存在;也就是说,一旦运行算法,我可以调用列表,它将为空,或者调用函数,它将使用列表。如果在函数外部,它是全局变量。如果在内部,只有在调用函数时才存在。

让我们现在尝试运行范围为(10, 50)的程序:

>>> evenNumbers(10, 50)

这是输出:

[10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50]

正如您所看到的,1050之间的偶数现在包括在列表中并打印出来。

让我们看另一个接受(name)字符串参数的函数的示例:

ch8_nameFunction.py

def nameFunction(name):
    print('Hello ' + name)

一旦运行了这个算法,我们就可以为任何名称调用该函数。看一下:

>>> nameFunction('Sofia')

我们得到以下输出:

Hello Sofia

让我们输入另一个名称:

>>> nameFunction('Katya')

我们将看到以下输出:

Hello Katya

正如您所看到的,一旦在算法中定义了一个函数并运行了算法,我们可以根据需要调用该函数。

现在,我们在先前的偶数示例中使用了迭代,所以是时候更仔细地看看文件、数据和迭代了,让我们暂停一下并看看。当我们进行迭代时,我们将遇到更多的函数示例。

学习有关文件、数据和迭代的知识

在本节中,我们将看看如何处理文件、数据和迭代与 Python。这将为我们提供有关如何使用算法来打开、编辑和运行已存在的 Python 文件或新文件的信息。通过迭代,我们将学习如何根据某些条件重复代码行,限制算法的某个部分将运行多少次或在什么条件下运行。我们将首先从文件开始。

在 Python 中处理文件

在 Python 中,与文件相关的主要函数是open()函数。如果要在相同目录中打开文件,可以使用以下命令行:

fOpen = open('filename.txt')

如果文件位于另一个位置,您需要找到文件的路径并在命令中包含该路径,如下所示:

fOpen = open('C:/Documents/filename.txt')

除了打开文件之外,我们还可以告诉程序做一些其他事情。它们列在下面:

  • r:用于打开文件进行读取(这是默认值,因此不需要包含);如果文件不存在,则创建新文件。

  • w:用于打开文件进行写入;如果文件不存在,则创建新文件。

  • a:用于打开文件以进行追加;如果文件不存在,则创建新文件。

  • x:用于创建指定的文件;如果文件已经存在,则返回错误。

除了列出的方法之外,还有两件事可以用于标识文件需要如何处理 - 即作为二进制还是文本:

  • t:文本(这是默认值,因此如果未指定,则默认为文本)

  • b: 二进制(用于二进制模式 - 例如图像)

看一下以下代码。由于这段代码是单行的,而且针对每个路径都是特定的,所以没有包含在存储库中:

fOpen = open('filename.txt', 'rt')

前面的代码与先前的代码相同,fOpen = open('filename.txt')。由于rt是默认值,不包括它们会导致算法的相同执行。

如果我们想要打开一个文件进行写入,我们可以使用fOpen = open('filename.txt', 'w')代码。在 Python 中关闭文件,我们使用close()代码。

文本文件的一些附加方法包括在以下列表中。这不是一个详尽的列表,但包含了一些最常用的方法:

  • read(): 可以用来读取文件的行;使用read(3)读取前 3 个数据。

  • tell(): 用于找到当前位置的字节数。

  • seek(): 将光标移动到原始/初始位置。

  • readline(): 读取文件的每一行。

  • detach(): 用于将底层二进制缓冲区与TextIOBase分离并返回它。

  • readable(): 如果可以读取文件流,则返回True

  • fileno(): 用于返回文件的整数编号。

正如你所看到的,你可以使用 Python 来操纵并从文本文件中获取信息。例如,当向现有文本文件添加代码行时,这可能会很有用。现在,让我们来看看 Python 中的数据。

Python 中的数据

在我们开始处理数据之前,让我们澄清一下,我们不是在讨论数据类型,这是我们在第一章中讨论的,计算机科学基础。在本章中,我们将主要以列表的形式查看数据和与数据交互的方式。

让我们看看我们可以用数据做些什么。

首先,从存储库中获取ch8_survey.txt文件。你将需要它用于下一个算法。

假设你让你的朋友们在蓝色、红色和黄色之间为一个团体标志进行选择。ch8_survey.txt文件包含了这些投票的结果。Python 允许我们操纵这些数据。例如,文件的第一行说Payton – Blue。我们可以使该行打印出Payton 投票支持蓝色。让我们看看算法:

ch8_surveyData.py

with open("ch8_survey.txt") as file:
    for line in file:
        line = line.strip()
        divide = line.split(" - ")
        name = divide[0]
        color = divide[1]
        print(name + " voted for " + color)

让我们分解我们的代码,以了解发生了什么:

  • 第一行打开了调查文件并保持打开状态。

  • 下一行,for line in file,将遍历文件中的每一行执行以下命令。

  • 然后算法将信息分割在破折号处。第一部分被定义为名字(name = divide[0]),第二部分被定义为颜色(color = divide[1])。

  • 最后,代码打印出了带有投票支持文本的行。

看一下以下输出:

Payton voted for Blue
Johnny voted for Red
Maxi voted for Blue
Jacky voted for Red
Alicia voted for Blue
Jackson voted for Yellow
Percy voted for Yellow

正如你所看到的,现在每一行都经过调整,去掉了破折号,并包括了投票支持短语。但是如果你想要计算投票呢?我们也可以为此编写一个算法:

ch8_surveyData2.py

print("The votes for Blue are in.")
blues = 0
with open("ch8_survey.txt") as file:
    for line in file:
        line = line.strip()
        name, color = line.split(' - ')
        if color == "Blue":
            blues = blues + 1
print(blues)

正如你所看到的,我们正在通过验证每一行并使用if color == "Blue"代码来计算投票。当我们运行这个算法时,我们得到以下输出:

The votes for Blue are in.
3 

正如你所看到的,该算法打印出了初始的print()命令,然后打印出了蓝色的计数投票。

我们也可以处理数据,找出诸如均值、中位数和众数之类的东西,但现在我们不会详细讨论。如果将来我们在某个应用问题中需要它们,我们会使用它们。然而,大多数这些特定于数据的问题将使用库来简化一些算法和计算。

现在,让我们再谈一下迭代,我们一直在使用,但需要进一步定义以更好地理解其用法。

在算法中使用迭代

在我们进入迭代之前,让我们定义一下术语。迭代意味着重复。当我们在算法中使用迭代时,我们正在重复步骤。想想我们之前在问题中使用过的for循环,比如偶数问题——算法迭代了一系列数字。它对我们范围内的所有数字进行了重复。

让我们看另一个例子:

ch8_colorLoop.py

colors = ['blue', 'green', 'red']
for color in colors:
    print(color)

该算法中的迭代是它将为原始颜色列表中的每种颜色重复执行print过程。

这是输出:

blue
green
red

如你所见,每种颜色都被打印出来了。

我们还可以从用户那里获取输入,然后迭代执行一些操作。例如,看看以下算法:

ch8_whileAlgorithm.py

ask = int(input("Please type a number less than 20\. "))
while ask > 0:
    print(ask * 2)
    ask = ask – 1

如你所见,我们将输入变量定义为ask。然后,只要ask大于0,我们就打印出加倍的数字并将数字减1

输出如下:

Please type a number less than 20\. 8
16
14
12
10
8
6
4
2

输出显示了通过将初始数字加倍而创建的列表,即8 x 2 = 16,然后继续直到ask变量不再大于0

接下来,让我们看看如何可以迭代遍历多个列表。该算法使用两个列表,然后使用两者的信息打印出一个语句:

ch8_Iterations.py

jewelry = ['ring', 'watch', 'necklace', 'earrings', 'bracelets'] 
colors = ['gold', 'silver', 'blue', 'red', 'black']
for j, c in zip(jewelry, colors):
    print("Type of jewelry: %s in %s color. " %(j, c))

当我们运行算法时,我们得到以下输出:

Type of jewelry: ring in gold color. 
Type of jewelry: watch in silver color. 
Type of jewelry: necklace in blue color. 
Type of jewelry: earrings in red color. 
Type of jewelry: bracelets in black color.

看一下print语句:print("Type of jewelry: %s in %s color." %(j, c))%s符号将分别被(j, c)的值替换。因此,第一个%s符号获取j的项目,第二个%s符号获取c的项目,其中j是来自珠宝列表的项目,c是来自颜色列表的颜色。

如你所见,我们可以以多种方式迭代列表。这只是一些示例,让我们熟悉循环和如何在我们的算法中使用信息。随着我们深入更复杂的问题,算法将变得更复杂,所以我们将重新讨论本章和之前章节中的许多主题。

在进入下一章之前,我们需要看一下面向对象编程OOP)。

使用面向对象编程

面向对象编程是一种将数据结构化为对象的方法。Python 程序是一种面向对象的程序,它将算法结构化为对象,以便根据属性和行为对它们进行打包。要了解面向对象编程,我们需要知道如何做以下事情:

  • 创建类

  • 使用类创建对象

  • 使用类继承来建模系统

在 Python 中,我们使用类来打包数据。要理解类,让我们创建一个:

>>> class Books:
	pass

然后我们可以调用Books()类并获取类在计算机上保存的位置。请注意,我的输出将与您的不同,因为类将保存在不同的位置:

>>> Books()

这是我的输出:

<__main__.Books object at 0x000001DD27E09DD8>

现在我们已经创建了类,我们可以向类添加书籍对象:

>>> a = Books()
>>> b = Books()

这些实例中的每一个都是Books()中的一个独特对象。如果我们要比较它们,由于它们是不同的,a == b将返回False

现在,让我们看一个创建地址簿的类。通过创建类,我们可以根据需要添加条目:

ch8_addressBook.py

class Entry:
    def __init__(self, firstName, lastName, phone):
        self.firstName = firstName
        self.lastName = lastName
        self.phone = phone

在这个算法中,我们创建了一个代表地址簿中条目的Entry类。一旦我们运行算法,我们就可以向地址簿添加条目并调用它们的信息。例如,看看以下代码:

>>> Johnny = Entry('John', 'Smith', '201-444-5555')

这段代码将Johnny输入地址簿。现在,我们可以分别调用 Johnny 的名字、姓氏和电话号码,如下所示:

>>> Johnny.firstName

这是我的输出:

'John'

我们可以调用姓氏:

>>> Johnny.lastName

这是获得的输出:

'Smith'

我们还可以调用电话号码:

>>> Johnny.phone

我们可以看到这个输出:

'201-444-5555'

我们可以添加任意多的条目,然后根据需要调用它们:

>>> max = Entry('Max', 'Minnow', '555-555-5555')
>>> emma = Entry('Emma', 'Dixon', '211-999-9999')
>>> emma.phone

这是我们的输出:

'211-999-9999'

添加条目后,我们可以调用姓氏:

>>> max.lastName

我们得到这个输出:

'Minnow'

如你所见,我们添加了两个新条目,然后调用了 Emma 的电话号码和 Max 的姓氏。

一旦我们有了类,我们就可以有一个类继承其他类的方法和属性。因为这些类继承了属性和方法,原始类被称为父类,而新类被称为子类

回到我们的地址簿,我们可以通过将类传递给新类来创建一个子类。我知道,这听起来很混乱,但看一下这个算法:

>>> class Job(Entry):
	pass

现在我们也可以使用子类添加更多项目。看下面的例子:

>>> engineer = Job('Justin', 'Jackson', '444-444-4444')

我们可以用类做更多的事情,但让我们尝试通过解决一个问题并设计一个算法,使用至少学到的一些组件来总结这一章。

问题 1 - 创建一个书库

假设你有很多书,想创建一个存储有关书籍信息的算法。你想记录每本书的标题、作者、出版日期和页数。为这种情况创建一个算法。

首先,让我们思考一下问题:

  • 我拥有的书的数量不断变化,所以我希望创建一个可以根据需要添加信息的东西。

  • 我也希望能够删除我不再拥有的书。

虽然我们可以使用库,但对于这个特定的问题,最好的解决方案是一个类。让我们从创建一个名为Books的类开始:

ch8_BookLibrary.py

class Books:

    def __init__(self, title, author, pubDate, pages):
        self.title = title
        self.author = author
        self.pubDate = pubDate
        self.pages = pages
book1 = Books('The Fundamentals of Fashion Design', 'Sorger & Udale', '2006', '176')
book2 = Books('Projekt 1065: A Novel of World War II', 'Gratz', '2016', '309')

如您所见,我们已经定义了我们的类。该类有四个参数:titleauthorpubDatepages。在定义之后,添加了两本书。当我们运行这个程序时,实际上什么都不会发生,但我们可以调用任一本书的信息:

>>> book1.title

我们将得到以下输出:

'The Fundamentals of Fashion Design'

我们将调用book2的出版日期:

>>> book2.pubDate

我们得到这个输出:

'2016'

您可以看到,我现在可以在运行算法后调用每本书保存的任何元素。

现在,让我们看看如何在 Python shell 中添加第三本书:

>>> book3 = Books('peanut butter dogs', 'Murray', '2017', '160')

由于书已经添加,我们也可以调用关于这本书的信息:

>>> book3.title

我们得到这个输出:

'peanut butter dogs'

我们可以调用book3的总页数:

>>> book3.pages

我们将得到以下输出:

'160'

如您所见,我们可以在算法中添加书籍到我们的类中,或者在 Python shell 中运行算法后添加书籍。现在,让我们看看另一个问题。

问题 2 - 组织信息

我们被要求创建一个算法,它接受三个数字作为输入,并提供这些数字的总和。我们可以用多种方法来做到这一点,但让我们看看如何使用eval()函数:

ch8_Sums.py

a = int(input("Provide the first number to be added. "))
b = int(input("Please provide the second number to be added. "))
c = int(input("Provide the last number to be added. "))
print(eval('a + b + c'))

请注意,我们将每个输入变量定义为int类型。这样定义是为了正确执行评估。

这是我们算法的输出:

Provide the first number to be added. 1
Please provide the second number to be added. 2
Provide the last number to be added. 3
6

如果我们忘记为每个数字添加类型,函数会将其评估为123,因为它只是将每个字符串添加到下一个字符串。所以,如果我们的输入是JohnMaryJack,我们的输出将是JohnMaryJack

我们之前没有涉及sum()函数。让我们看看如何使用该函数:

ch8_Sums2.py

a = int(input("Provide the first number to be added. "))
b = int(input("Please provide the second number to be added. "))
c = int(input("Provide the last number to be added. "))
numbers = []
numbers.append(a)
numbers.append(b)
numbers.append(c)
print(sum(numbers))

在这种情况下使用sum需要我们将输入添加到列表中,因为sum()可以处理可迭代对象,比如列表。虽然这种解决方案代码更多,但输出与我们的eval()函数完全相同,如下所示:

Provide the first number to be added. 1
Please provide the second number to be added. 2
Provide the last number to be added. 3
6

如您所见,我们使用不同的 Python 函数得到了与之前相同的答案。在我们继续下一章之前,让我们再看一个问题。

问题 3 - 循环和数学

对于这个问题,我们必须创建一个算法,打印出给定范围内所有数字的平方。记住,我们可以单独打印每个数字,但如果范围很大,最好我们有一个列表。我们还需要在范围内进行迭代,并且如果我们想要包括端点,我们必须将最大值加 1。

看一下以下算法:

ch8_SquareLoops.py

print("This program will print the squares of the numbers in a given range of numbers.")
a = int(input("What is the minimum of your range? "))
b = int(input("What is the maximum of your range? "))
Numbers = []
b = b + 1
for i in range(a, b):
    j = i**2
    Numbers.append(j)
    i = i + 1
print(Numbers)

注意我们在算法中添加了一个j变量。我们没有使用i = i**2,因为那样会改变i的值,这会影响算法中的迭代。通过使用j,我们可以使用i来遍历给定的范围。让我们来看看我们的输出:

This program will print the squares of the numbers in a given range of numbers.
What is the minimum of your range? 4
What is the maximum of your range? 14
[16, 25, 36, 49, 64, 81, 100, 121, 144, 169, 196]

该算法打印出我们提供范围的平方列表。它还有一个初始的print语句,解释了代码将要做的事情。

现在我们已经看了一些例子,让我们来看看父类和子类以及继承是如何工作的。

使用继承

在 Python 中,我们可以使用继承将方法和属性从一个类传递到另一个类。父类具有将被继承的方法和属性。子类继承自父类。父类是一个对象,子类也是一个对象,所以我们正在定义类的属性。

让我们看看如何使用一个科学例子。我们将创建一个名为mammals的类。并不是所有的哺乳动物都是胎生的。哺乳动物在产下活仔时是胎生的。不是胎生的哺乳动物是鸭嘴兽。鸭嘴兽产卵而不是产下活仔。如果我们要为此编写一个算法,我们希望动物继承父类的特征 - 在这种情况下是mammals。让我们看看下面的代码片段:

ch8_mammals.py

class mammals:
     def description(self):
       print("Mammals are vertebrate animals.")

     def viviparous(self):
       print("Mammals are viviparous, but some are not.")

class monkey(mammals):
     def viviparous(self):
       print("Monkeys are viviparous.")

class platypus(mammals):
     def viviparous(self):
       print("The platypus is not viviparous. It's an egg-laying mammal.")

obj_mammals = mammals()
obj_monkey = monkey()
obj_platypus = platypus()

obj_mammals.description()
obj_mammals.viviparous()

obj_monkey.description()
obj_monkey.viviparous()

obj_platypus.description()
obj_platypus.viviparous()

从前面的代码中,注意到mammals()类使用了一个描述,然后是有关哺乳动物和胎生的信息。monkey使用了与mammals类相同的描述,但是包括了一个不同的胎生声明。platypus也是一样的情况。monkeyplatypus类都是mammals类的子类。

这三个类,父类和两个子类,然后被简化为一个变量,以便通过调用该变量来使用。最后,算法打印出了父类和两个子类的描述和胎生声明。让我们来看看输出:

Mammals are vertebrate animals.
Mammals are viviparous, but some are not.
Mammals are vertebrate animals.
Monkeys are viviparous.
Mammals are vertebrate animals.
The platypus is not viviparous. It's an egg-laying mammal.

正如你所看到的,所有三个类都使用了相同的描述。这是因为我们没有对子类的描述进行任何更改。当我们定义类时,我们只改变了想要与父类不同的部分。其他所有内容都是从父类继承而来的。

在 Python 中,父类和子类被广泛用于多种目的。例如,在游戏中,你可能会有一些具有相同特征的敌人。我们可以创建一个包含所有共同特征的父类,然后将所有敌人的个体特征作为子类进行更改,而不是分别定义每个敌人的所有特征。这只是继承可以使用的方式之一。还有许多其他用途,它可以帮助我们节省时间,并通过只用父类定义它们来避免错误。

现在我们已经有了一些类的经验,并学习了面向对象编程,让我们结束本章。

摘要

在本章中,我们讨论了 Python 编程的基础知识。我们研究了一些内置函数,使用了字典和列表,使用了变量和函数,学习了有关文件、数据和迭代的知识,并学习了类和面向对象编程。

正如我们在本章和解决以前的问题时提到的,Python 为我们提供了多种解决同一问题的方法。其中一个例子就是在本章的“问题 2 - 组织信息”部分提供的,我们在两种不同的算法中使用了eval()sum()函数来产生相同的结果。随着我们继续学习 Python 以及如何编写我们的算法,选择使用哪些函数、变量、参数等将开始变得自然而然。本章中一些更具挑战性的概念和内容涉及数据分析,比如我们在介绍 Python 中的数据时使用的调查,以及类。在这些领域进行一些练习是很重要的,这样你就能更好地理解这些主题。

在这一章之后,你现在可以使用内置函数,区分列表、字典和类,并解决结合多个不同概念的问题,比如变量、迭代和列表。在下一章中,我们将更深入地研究输入和输出,以帮助我们设计算法。

第九章:理解输入和输出以设计解决方案算法

在本章中,我们将深入研究问题,以确定设计问题算法所需的输入和输出。我们将使用我们在第八章中学到的概念,Python 简介,在那里我们讨论了面向对象的编程、字典、列表等。当您练习获取输入并在算法中使用它时,您将能够看到算法的输出取决于输入信息。

在本章中,我们将涵盖以下主题:

  • 定义输入和输出

  • 理解计算思维中的输入和输出

在本章中,我们将重点关注理解不同类型的输入以及如何使用Python编程语言中的输出。为了更好地理解这些主题,我们首先要看一下它们的定义。

技术要求

对于本章,您需要安装最新的 Python 版本。

您可以在此章节中找到使用的源代码:github.com/PacktPublishing/Applied-Computational-Thinking-with-Python/tree/master/Chapter09

定义输入和输出

我们将从研究输入及其定义开始,这反过来又用于提供结果或输出。

输入是放入系统或机器中的东西。在计算机中,我们有输入设备,计算机会解释这些设备以提供结果或输出。当我们看不同类型的输入以及如何在算法中使用输入时,您将看到我们从这些算法中获得的输出。让我们看一些输入设备的例子:

  • 键盘:当我们使用键盘时,计算机会解释按键,并在各种程序中使用该解释,比如文档编写和搜索栏。

  • 鼠标:鼠标用于在我们的物理屏幕上导航,帮助我们点击内容和滚动页面。

  • 操纵杆:操纵杆可用于玩电脑游戏。

  • 麦克风:在更现代的机器中,麦克风不仅用于通过电话和视频应用进行通信,还用于接受人工智能AI)助手如CortanaSiri的口头命令,以及语音转文字命令。

所有上述输入都被编程到我们的计算机中以便使用。在 Python 中,我们编写使用这些输入的算法。用户输入是指程序询问用户将用于生成输出的信息。请记住,输入和输出可以在算法中的任何时间发生。我们还可以提供需要用户额外输入的输出反馈,依此类推。让我们看看如何从 Python 获取用户输入。

Python 有一个主要的用户提示,即input()函数。此函数用于接受用户输入,然后自动将输入转换为用户期望的输出。

我们之前在多个程序中使用过这些主要提示,比如第五章中的商店示例,探索问题分析。让我们看看算法中一些输入命令的样子。看看以下代码片段:

ch9_input1.py

name = input("What is your name? ")
print("Nice to meet you " + name + ".")

片段要求用户输入他们的名字,然后使用输入的信息打印一个声明。这段代码的结果如下:

What is your name? Mikayla
Nice to meet you Mikayla.

现在,我们可以只要求输入而不使用任何提示问题,但用户将不知道正在被问及什么或者它将如何被使用。看看没有提示的片段,如下所示:

ch9_input2.py

name = input()
print("Nice to meet you " + name + ".")

当我们运行上述程序时,在我们的 shell 中没有打印任何内容。然而,我知道我需要输入一些东西,所以看看当我只输入一个字母时发生了什么:

d
Nice to meet you d.

正如你所看到的,用户不会知道该怎么做,因为窗口没有询问任何事情;它只是给了一个空白的空间,用户必须假设那是输入的地方。但这可能会导致很多混乱,因为没有告诉用户是否需要输入,或者他们需要输入什么样的东西。

我们可以通过使用 print 语句来减轻一些混乱,例如,通过向用户提供关于我们将要询问的输入的信息。我们还可以在 input() 函数中包含一条语句。例如,我们可以使用这样的一行代码 name = input('你叫什么名字?'),这样首先会显示带引号的语句,这样用户就知道我们在问什么。无论哪种方式,提供一条语句让用户知道需要什么是我们设计算法时非常重要的。

正如你在前面的例子中看到的,input() 函数接受一个字符串,然后在 print() 语句中使用该字符串。让我们看看使用 input 命令时列表会是什么样子:

ch9_input3.py

#Create the list
names = []

#Ask user how many names will be added
name = int(input("How many names will be in the list? ")) 

#Iterate to add each name to the list
for i in range(0, name): 
    people = str(input()) 

    names.append(people) 

print(names)

在上面的片段中,我们首先创建了列表,这不会显示给用户。然后询问用户将有多少个名字添加到列表中。之后,我们遍历列表,以便用户可以分别输入每个名字。最后,名字被添加到列表中并打印出来。看一下这个算法的输出:

How many names will be in the list? 4
Manny
Lisa
John
Katya
['Manny', 'Lisa', 'John', 'Katya']

注意,名字没有提示,所以算法假设用户在输入值 4 后会知道该怎么做。这可以通过在迭代中简单添加来减轻。看一下下面的代码片段:

ch9_input4.py

#Create the list
names = []

#Ask user how many names will be added
name = int(input("How many names will be in the list? ")) 

#Iterate to add each name to the list
for i in range(0, name): 
    people = input("Type the next name on the list. ") 

    names.append(people) 

print(names)

正如你所看到的,每个名字现在都有一个提示,要求输入下一个名字,当算法运行时,输出看起来像下面的样子:

How many names will be in the list? 4
Type the next name on the list. Milo
Type the next name on the list. Sonya
Type the next name on the list. Gabriel
Type the next name on the list. Maxine
['Milo', 'Sonya', 'Gabriel', 'Maxine']

上面的输出显示了完成的列表以及添加到该列表中的每个名字的提示。在算法中对这些提示进行简单的添加可以减轻用户在输入时的混乱。

正如你从本节中前面的片段中看到的,输入被读取为整数、字符串和列表。Python 自动转换了信息,以便算法可以使用它。

在 Python 中,还有一种方法可以使用一行代码从多个输入中定义多个变量。看一下下面的代码片段:

ch9_input5.py

name1, name2 = input("Enter First Name: "), input("Enter Last Name: ")
print(name1 + " " + name2)

正如你在前面的代码中所看到的,print() 命令中的引号(" ") 用于分隔输入。看一下这个算法的输出:

Enter First Name: John
Enter Last Name: Doe
John Doe

正如你所看到的,程序要求输入名字,这些名字在算法的第一行中被调用。这绝不是我们从用户那里获取输入的唯一方式,也不是我们将使用的唯一输入。

在本节中,我们看了一些在算法中定义输入的方法。我们还学会了如何从用户那里获取输入以便稍后在算法中使用。现在让我们转到下一节,我们将在其中看一些计算思维问题中的输入和输出。

理解计算思维中的输入和输出

为了更好地理解输入和输出,我们将在本节中看一些计算思维问题。当我们设计算法的过程中,我们将专注于确定算法所需的输入以及我们从算法中需要的输出。让我们来看看我们的第一个问题。

问题 1 - 构建凯撒密码

凯撒密码是一种用于编码消息的密码系统。密码学用于保护信息,以便只有预期的用户可以阅读消息。凯撒密码使用字母的移位来编码消息。例如,字母 a 移动 3 个位置将变成 d。为了构建一个可以为我们做到这一点的算法,我们需要一些东西:

  • 将要编码的消息

  • 我们将每个字母移动多少

  • 一个打印的编码消息

让我们思考一下前面提到的要点意味着什么。消息将需要用户输入。移位也将是一个输入,因为我们不希望程序总是使用相同的代码。否则,一旦知道了移位,原始消息就很容易被破解。打印的编码消息是我们的输出。以下是一些步骤,以帮助创建算法:

  1. 首先,我们需要输入消息。

  2. 然后我们需要定义输入移位。

  3. 之后,我们需要遍历每个字母。

  4. 迭代后,我们根据定义的移位调整每个字母。

  5. 最后,我们打印出新的编码消息。

我们将通过以下密码算法片段来举例说明前面的步骤:

ch9_problem1.py

#Print initial message for the user
print("This program will take your message and encode it.")
#Ask for the message
msg = input("What message would you like to code? ")
#Ask for shift
shift = int(input("How many places will you shift your message? "))
msgCipher = ""
#Iterate through the letters, adjusting for shift
for letter in msg:
  k = ord(letter)
  if 48 <= k <= 57:
    newk = (k - 48 + shift)%10 + 48
  elif 65 <= k <= 90:
    newk = (k - 65 + shift)%26 + 65
  elif 97 <= k <=122:
    newk = (k - 97 + shift)%26 + 97
  else:
    newk = k
  msgCipher += chr(newk)
print("Your coded message is below.")
print(msgCipher)

请注意,在迭代中,我们正在进行一些数学运算,以找出字母表中每个字母的值。我们使用一些条件语句来定义在使用用户定义的移位值后,每个字母的值是什么。

让我们看看当我们运行此算法时产生的输出:

This program will take your message and encode it.
What message would you like to code? Code this message
How many places will you shift your message? 2
Your coded message is below.
Eqfg vjku oguucig

正如您可以从前面的输出中看到的,注意消息编码消息中的第一个单词 - Code

  • C,向后移动两个字母,现在是E

  • o现在是q,依此类推。

对于消息中的每个字母,字母都向后移动了两个位置。输出是我们的print()语句加上编码消息,由行print(msgCipher)给出。我们的输出包括两个语句以便清晰。第一个消息是必要的吗? 不。但是,在某些算法中,最好有一些行,让用户知道算法的运行情况。

让我们看看另一个问题。

问题 2 - 查找最大值

您被要求创建一个程序,该程序从数字列表中找到最大值。数字列表由用户提供。因此,为此问题创建一个算法。

首先,我们需要确定此特定问题的输入和输出。它们列如下:

  1. 列表中的项目数(输入)

  2. 列表中的数字(输入)

  3. 列表中的最大值(输出)

回想一下本章前面,在定义输入和输出部分,我们可以定义一个空列表,然后要求用户让程序知道将有多少项目输入列表。以下程序举例说明了相同的情况:

ch9_problem2.py

#Define the list name
maxList = []
#Ask user how many numbers will be entered
quant = int(input("How many data points are you entering? "))
#Iterate, append points, and find maximum
for i in range(0, quant):
    dataPoint = int(input("Enter number: "))
    maxList.append(dataPoint)
#Print maximum value
print("The maximum value is " + str(max(maxList)) + ".")

请注意,从前面的代码中,我们使用了max()函数来找到列表中的最大值。此外,我们还必须添加int()类型,以便算法正确识别值为数字。代码遍历从0到我们从用户输入中收到的数据点数,我们将其定义为quant变量。当算法遍历数字时,它会比较它们并找到最大值。

让我们看看输出是什么样子的:

How many data points are you entering? 6
Enter number: 1
Enter number: 2
Enter number: 8
Enter number: 4
Enter number: 5
Enter number: 7
The maximum value is 8.

正如您可以从前面的输出中看到的,用户声明将输入6个数字。然后算法提示用户输入每个值。最后,识别出最大值。

现在让我们看看如何构建一个猜数字游戏。

问题 3 - 构建一个猜数字游戏

您被要求构建一个游戏,用户可以根据需要获得尽可能多的机会来按正确顺序识别四位数的数字。当用户输入猜测时,算法将指出是否有任何数字是正确的。

算法将确定数字是否在正确的位置,但不会确定哪些数字是正确的,哪些在正确的位置。如果玩家猜对了数字,游戏就结束了。您可能会发现这个游戏类似于一个名为Mastermind的流行游戏,其中玩家将四个颜色代码插入游戏,第二个玩家猜测。

在这种情况下,我们使用数字而不是颜色,计算机程序将帮助我们确定是否有任何数字是正确的,以及它们是否在正确的位置。

为了构建这个游戏,让我们看看我们需要的输入和输出:

  • 随机生成的 4 位数(输入)

  • 用户的猜测(输入)

  • 来自算法的数字和位置的反馈(输出)

  • 在数字被猜中后,结束游戏的消息(输出)

当我们生成数字时,我们需要找到一种比较该数字的数字与用户输入的数字的方法。请注意,我们需要为这个特定的算法导入random库,以便我们可以生成随机的四位数。让我们看一下代码片段:

ch9_problem3.py

import random as rand
number = rand.randint(1000,10000)
guess = int(input("What's your first guess? ")) 
#Algorithm checks if number is correct. 
if (guess == number): 
	print("That's right! You win!") 
else: 
        i = 0

正如你所看到的,这是我们需要用户输入的一个例子。然而,这只是第一次猜测,所以我们需要从用户那里获取更多的输入,并提供一些提供反馈的输出。看一下来自同一算法的以下代码片段:

#Condition so that user keeps guessing until they win.
        while (guess != number):
            i = i + 1
            #Remember you can also write as i += 1
            j = 0
            guess = str(guess) 
            number = str(number) 
            #Check which numbers are correct and mark incorrect with 'N'
            guessY = ['N']*4
            #Make sure you check each digit, so run loop 4 times 
            for q in range(0, 4):
                    if (guess[q] == number[q]): 
                            j += 1 
                            guessY[q] = guess[q] 
                    else: 
                            continue
            #If only some digits are correct, run next condition 
            if (j < 4) and (j != 0): 
                    print("You have " + str(j) + " digit(s) right.") 
                    print("These numbers were correct.") 
                    for m in guessY: 
                            print(m, end = " ") 
                    #Ask for next input
                    guess = int(input("What is your next guess? ")) 

注意,我们需要在每次猜测失败后询问每次猜测。算法需要知道下一次猜测是什么,以便在用户没有识别出正确的四位数时继续运行。这就是为什么猜测输入值在算法中出现在多个地方。

一旦用户猜对了,程序需要打印最终的声明,在这种情况下,我们会让用户知道猜测花了多少次:

            #If only some digits are correct, run next condition 
            if (j < 4) and (j != 0): 
                    print("You have " + str(j) + " digit(s) right.") 
                    print("These numbers were correct.") 
                    for m in guessY: 
                            print(m, end = " ") 
                    #Ask for next input
                    guess = int(input("What is your next guess? ")) 
            #No digits correct
            elif (j == 0): 
                    print("None of these digits are correct.") 
                    guess = int(input("What is your next guess? ")) 
        if guess == number: 
            print("It took you " + str(i)+ " tries to win!") 

正如你所看到的,这个算法在多个地方基于满足条件的输入,以及输出。输出包括print语句和对正确或错误数字的反馈。让我们看看程序执行时输出的样子:

What's your first guess? 1111
None of these digits are correct.
What is your next guess? 2222
None of these digits are correct.
What is your next guess? 3333
You have 1 digit(s) right.
These numbers were correct.
3 N N N What is your next guess? 3444
You have 3 digit(s) right.
These numbers were correct.
3 4 N 4 What is your next guess? 3454
You have 3 digit(s) right.
These numbers were correct.
3 4 N 4 What is your next guess? 3464
You won in 6 attempts.

请注意,在第三次尝试后,我们有一个正确的数字,表示为3 N N N。这意味着第一位数字是 3。然后我们需要猜测其余的数字。在程序中提供反馈,或输出,使用户能够继续这个游戏。

正如你所看到的,理解输入和输出使我们能够创建能够提供反馈、根据条件进行迭代等等的程序。

在这一部分,我们学习了如何在解决问题时使用输入和输出。我们提供了一些场景,让我们可以探索一些输入类型,比如用户定义的输入和算法定义的输入。我们还学习了如何编写算法,为用户提供清晰的输出。

总结

在本章中,我们讨论了 Python 算法中的输入和输出。输入是我们获取算法响应的信息的方式。例如,用户可以为需要提示信息的算法提供输入,就像我们在示例问题中看到的那样。Python 和其他编程语言也可以从鼠标、扫描仪、摄像头和程序交互的其他设备中获取输入。

为了编写我们的算法,重要的是要确定我们需要什么输入,我们在设计中何时需要它们,以及我们需要程序输出什么。在[第三章](B15413_03_Final_SK_ePub.xhtml#_idTextAnchor056),理解算法和算法思维中,我们使用用户输入来找到午餐的成本。我们创建了一个字典来帮助我们找到这些信息。同样,在阅读完本章之后,你现在有了构建算法来解决一些额外问题的技能,比如我们的数字猜测游戏和我们的最大查找器。

在设计算法之前,确定输入和输出是至关重要的,因为我们在算法中做出的条件和决策取决于输入和我们需要程序提供的输出。

在下一章中,我们将讨论控制流,这在理解算法的阅读方式和指令执行方式上至关重要。我们还将在探索控制流中更仔细地查看函数。

第十章:控制流

在本章中,我们将深入研究问题,并确定设计算法所需的输入和输出。在本章中,你将学习算法的阅读方式以及指令执行的顺序。你还将学习如何使用函数和循环来操纵算法中的控制流。

在本章中,我们将涵盖以下主题:

  • 定义控制流及其工具

  • 使用 if、for 和 range()以及其他控制流工具

  • 使用循环和条件语句

  • 重新审视函数

到本章结束时,我们将学习控制流是如何定义的,我们在计算思维中设计算法时如何使用ifforrange()功能,以及如何将这些功能纳入算法中的函数定义中。让我们先来看看什么是控制流。

技术要求

你需要最新版本的 Python 来运行本章中的代码。你可以在这里找到本章使用的完整源代码:github.com/PacktPublishing/Applied-Computational-Thinking-with-Python/tree/master/Chapter10

定义控制流及其工具

简单地说,控制流是算法读取和执行指令、函数和语句的顺序。控制流意味着计算机会做出决定。想想看:当我走出大楼时,我要么用伞,要么不用伞。这取决于天气是否下雨(我是否记得带伞,我猜)。根据这个条件,我们做出决定。这个过程就是算法设计中的控制流程。

让我们看看 Python 编程语言中可用的一些控制流语句。

  • 如果

  • 对于

  • 范围()

  • 中断

  • 继续

  • 列表推导

  • 通过语句

  • 尝试语句

这些工具允许算法做一些事情,比如只要满足某个条件或一组条件,就可以运行,当条件发生时停止或中断,仅在一定范围内继续等等。让我们更仔细地看看其中一些工具。

使用 if、for 和 range()以及其他控制流语句

让我们从if语句开始,我们在第四章中首次讨论了这些内容,理解逻辑推理。这可能是算法设计中最常用和最知名的语句。你可能还记得在学习推理和证明时,在几何学中学习了条件语句。在那些课程中,你会以如果-那么的格式编写语句。接下来是一个例子:

下雨时,我穿雨衣。

这不是一个条件语句,至少现在不是。如果我们要将它写成条件句,那么我们就必须以如果-那么的格式来写,就像这个句子一样。看看接下来转换后的语句:

如果下雨,我就穿雨衣。

正如你所看到的,我们在日常生活中使用条件。我们只是没有指出它们。

在编写算法时,我们必须明确陈述算法需要做什么,所以我们必须明确陈述这些语句。在编程中,我们必须陈述每个条件。此外,如果我们有一系列需要满足的条件,有时我们需要嵌套这些语句。这最好通过一些例子来解释。让我们从如何使用嵌套语句开始。

使用嵌套 if 语句

即使在日常生活中,某些条件也取决于其他条件。例如,如果是星期一,我们就必须去上班。如果我们必须去上班而且下雨了,我们可能需要一把伞。但如果我们不去上班(假设我们呆在家里),我们就不需要检查是否需要伞。嵌套语句也是如此。我们使用它们来检查一个条件,然后是另一个条件,这是嵌套的。

假设我们正在玩一个掷骰子游戏。你掷骰子并得到如下分数:

  • 2、4 或 6 = 10 分

  • 1 或 3 = 5 分

  • 5 = 0 分并清除所有之前的点数

每一轮,玩家都必须掷两次骰子。让我们看一个包含第一次掷骰子的流程图,如下所示:

图 10.1 - 第一次掷骰子得分的流程图

图 10.1 - 第一次掷骰子得分的流程图

从流程图中可以看出,得分取决于掷出的数字。现在,假设你掷出了1。然后,让我们看看你的第二次掷骰子可能是什么样子,如下图所示:

图 10.2 - 在掷出 1 后进行第二次掷骰子的流程图

图 10.2 - 在掷出 1 后进行第二次掷骰子的流程图

正如你所看到的,玩家有三种可能的总得分。为了将其转化为算法,我们需要做一些事情:

  1. 定义掷骰子。

  2. 根据掷出的数字确定得分。

  3. 再掷一次。

  4. 根据第二次掷骰子确定最终得分。

首先让我们定义那个点数。我们需要虚拟地掷骰子。这意味着我们需要程序在16之间选择一个数字。让我们看看我们如何编写代码。请记住,这只是包含在这个文件中的更大代码的一部分。我们将在随后的片段中使用第二个ready(ready2):

ch10_rollDice.py

import random as rand
print("Let's play a game. ")
print("You get 5 points for rolling 1 or 3\. You get 5 points for rolling 2, 4, or 6.")
print("You lose all points if you roll a 5 in either round.")
ready = input("Are you ready to begin? Type r to roll. ")
score = 0
if ready == 'r':
    roll = rand.randint(1, 6)
    print('You rolled a ' + str(roll) + '.')
    if (roll == 1) or (roll == 3):
        score = 5
    elif (roll == 5):
        score = 0
    else:
        score = 10
    ready2 = input('Your round 1 score is ' + str(score) + '. 

请注意,在上面的代码片段中,我们需要为这个特定的算法导入random库。将其导入为rand而不是random可以让我们缩短一些代码,从而减少输入。因此,我们使用rand.randint()而不是random.randint()。我们需要random库,因为我们希望能够访问随机整数函数,以便算法可以在16之间选择一个数字(包括两个端点)。让我们看看当前的输出:

Let's play a game. 
You get 5 points for rolling 1 or 3\. You get 5 points for rolling 2, 4, or 6.
You lose all points if you roll a 5 in either round.
Are you ready to begin? Type r to roll. r
You rolled a 3.
Your round 1 score is 5\. 

从上面的输出中可以看出,首先提供了说明,然后一旦玩家按下r键选择掷骰子,游戏就会选择一个随机整数并显示出来。它还显示了第一轮后的当前得分。现在,让我们看看算法的下一部分,即进行第二次掷骰子。请注意,缩进遵循前面的代码片段。这个片段也包含在之前的较大文件中:

ready2 = input('Your round 1 score is ' + str(score) + '. Type r to roll again. ')
    roll2 = rand.randint(1, 6)
    print('You rolled a ' + str(roll2) + '.')
    if (roll2 == 1) or (roll2 == 3):
        score += 5
    elif (roll2 == 5):
        score = 0
    else:
        score += 10
    print('Your final score is ' + str(score) + '.')

当我们运行新代码时,它会显示第一轮得分,两次掷骰子和游戏的最终得分。请记住,如果我们在第二轮中掷出5,我们将失去所有的点数:

Let's play a game. 
You get 5 points for rolling 1 or 3\. You get 5 points for rolling 2, 4, or 6.
You lose all points if you roll a 5 in either round.
Are you ready to begin? Type r to roll. r
You rolled a 2.
Your round 1 score is 10\. Type r to roll again. r
You rolled a 6.
Your final score is 20.

正如你所看到的,程序为我们打印了一些内容。首先,我们掷出了26,所以我们没有失去我们的点数。其次,26都有 10 分,最终得分为 20 分。但让我们回顾一下嵌套的if语句。

要开始游戏,我们必须验证玩家是否准备好掷骰子。第一个if语句是必要的,以便继续进行其他决定。我们可以在没有许可的情况下掷骰子吗? 可以。但想想所有的游戏,包括传统棋盘游戏的应用版本。在这些应用中,玩家总是按下按钮或骰子来玩。这是一个类似的情况。

在我们说好准备掷骰子之后,就必须对点数做出决定。我们再次需要使用ifelifelse语句来循环遍历各种选项。现在,让我们看看何时可以使用for循环和range

使用 for 循环和 range

我们需要首先讨论的是for循环中有时会对变量和for循环条件产生一些混淆。为了理解我的意思,让我们看一个例子:

ch10_forLoop1.py

for letter in 'mountain':
    print(letter)

在上面的代码片段中,letter不是一个变量。它只是告诉 Python 我们想要迭代单词mountain中的每个字符。不过,我可以随便起名字。如果我写成下面这样,程序会做完全相同的事情:

for pin in 'mountain':
    print(pin)

在每种情况下,使用letterpin或者任何让我当时感到高兴的单词,程序的输出看起来像这样:

m
o
u
n
t
a
i
n    

正如您所看到的,Python 迭代了单词mountain中的每个字母,并将其打印到控制台上。同样的事情也可以用范围内的数字来做。例如,如果我想打印出110的数字,我可以使用for循环,如下所示:

ch10_forLoop2.py

for num in range(1, 11):
    print(num)

等等,我说我想要 1 到 10 的数字,为什么范围函数中有 11 呢?那是因为range()函数总是包括范围内的最小值,但不包括上限。因此,我们需要在我们的顶部数字上加1。让我们看看这个程序的输出是什么样子的:

1
2
3
4
5
6
7
8
9
10

正如您所看到的,每个数字都打印在单独的一行上。因此,让我们看看如果我们只想将这些数字添加到一个列表中会怎么样。我们可以将它们附加到一个新列表中,然后打印出列表。这对某些类型的算法和游戏非常有帮助。但在我深入讨论这些之前,让我们首先看看如何使用几行代码附加这些数字:

ch10_forLoop3.py

myNumbers = []
for num in range(1, 11):
    myNumbers.append(num)
print(myNumbers)

现在,当您打印列表时,请注意。如果您正确缩进了它,它将在每次附加新数字时都打印列表。但如果您的缩进是正确的,您只会打印最终列表,使输出看起来如下:

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

正如您所看到的,我们现在有了相同信息的表示。在这种情况下,我们打印出了数字列表。如果您试图将特定的东西附加到列表中,即使是用户输入,这是很有帮助的。在下一节中,当我们看while循环时,我们将看另一个列表问题,但在我们继续之前,让我们再看一个for循环问题和算法。

假设您想要一个算法,它将打印出一系列数字的立方。有一个条件:范围将根据用户输入而变化。让我们看看一个可以做到这一点的算法:

ch10_forLoop4.py

print('This program will provide a list of cubes for you. ')
minRange = int(input('What\'s the minimum for your range? '))
maxRange = int(input('What\'s the maximum for your range? '))
listOfCubes = []
for value in range(minRange, maxRange+1):
    number = value**3
    listOfCubes.append(number)
print('Your list of cubes in the range(' + str(minRange) + ', ' \
      +str(maxRange) + ') is: ')
print(listOfCubes)

让我们从前面的程序中注意几点:

  • 我们将输入转换为int类型。我们这样做是为了可以在for循环内使用数字进行数学表达式。

  • 我们有一个空列表,所以我们可以将立方附加到它上面。

  • 我们将maxRange1,因为如果不这样做,该数字将不包括在我们的结果中。

  • 我们有一个基于两个变量的范围,这两个变量是用户输入。

  • 我们有一个打印出的值列表。

当我们输入range(3, 6)时,程序看起来像这样:

This program will provide a list of cubes for you. 
What's the minimum for your range? 3
What's the maximum for your range? 6
Your list of cubes in the range(3, 6) is: 
[27, 64, 125, 216]

正如您所看到的,程序接受了提供的输入,最小值为3,最大值为6,并创建了该范围内数字的立方的列表。

重要提示:

记住,要得到指数,我们在 Python 中使用**符号。所以,2**243**481,等等。

记住,控制流是事情完成的顺序。通过for循环和range,我们要求程序重复执行事情,而不必每次输入一行,有效地操纵控制流以循环而不是一行接着一行地做事情。

现在我们已经看过了for循环和range,让我们看看在 Python 中我们可以以其他方式进行迭代的方法。

使用其他循环和条件

Python 提供了各种迭代信息的方法,除了for循环之外,还有while循环。使用while循环时,我们不断检查条件。再次,通过看一个例子来理解这一点会更容易。

假设我们正在玩一个游戏,并要求用户提供尽可能多的动物。我们希望能够做一些事情:

  1. 检查玩家是否有要添加的动物。

  2. 如果是,将每个动物附加到列表中。

  3. 如果没有,结束程序并提供最终列表。

  4. 提供最终列表和玩家能够输入的动物数量。

让我们看一个为这个游戏做基本工作的算法:

ch10_whileLoop1.py

myAnimals = []
print('Let\'s see how many animals you can name. Get ready!')
readyPlayer = input('When you are ready to begin, type y. ')
while readyPlayer == 'y':
    if readyPlayer == 'y':
        animalAdd = input('Name an animal. ')
        myAnimals.append(animalAdd)
        readyPlayer = input('Ready for the next one? Type y for yes or n for no. ')
howMany = len(myAnimals)
print('You were able to name ' + str(howMany) + ' animals. Here\'s your list: ')
print(myAnimals)

让我们稍微解释一下这段代码:

  1. 我们创建了一个空列表myAnimals

  2. 然后我们问玩家是否准备好了。

  3. 如果答案是肯定的,我们就会要求输入一个动物。

  4. 然后,我们检查他们是否还有更多要添加的。

  5. 我们检查了列表中添加了多少项。

  6. 我们打印了列表。

请注意,我们必须使用while循环和if语句。这是因为我们希望继续询问玩家添加动物,直到他们完成或无法想到更多要添加的动物为止。因此,只要玩家仍然可以添加项目,我们希望程序继续询问问题。但是如果玩家完成了,那么他们可以拒绝添加另一个动物,从而结束程序。

现在,我想明确一点,这个程序可以做得更好。我们可以添加一个计时器,看看在 30 秒内可以输入多少项。当玩家试图输入动物而不是先说“是”时,我们也可以添加一个条件来中断程序并解释为什么这样做。你可以为玩家试图输入已经在列表中的动物添加一个条件。你的挑战是尝试将这些组件添加到现有的程序中!

但是让我们看看当我们运行它时,这个程序是什么样子的:

Let's see how many animals you can name. Get ready!
When you are ready to begin, type y. y
Name an animal. bird
Ready for the next one? Type y for yes or n for no. y
Name an animal. dog
Ready for the next one? Type y for yes or n for no. y
Name an animal. cat
Ready for the next one? Type y for yes or n for no. y
Name an animal. mouse
Ready for the next one? Type y for yes or n for no. y
Name an animal. elephant
Ready for the next one? Type y for yes or n for no. n
You were able to name 5 animals. Here's your list: 
['bird', 'dog', 'cat', 'mouse', 'elephant']

请注意,这个程序的写法要求我们每次都要回答是否要添加一个动物。这是因为只要我们回答“是”,条件就会继续运行。但一旦我们回答“否”,程序就会结束,给我们动物列表和我们能够命名的数量。

现在,让我们再看一个while循环。这一次,我们将检查条件是否为True。看一下以下代码片段:

ch10_whileLoop2.py

while True:
    num = int(input('Please enter an integer 0 through 9\. '))
    if num in range(0, 10):
        print(num)
    else:
        print('That\'s not in range. ')
        break

在前面的算法中,控制流规定我们反复要求一个介于09之间的数字。只要我们给它一个介于09之间的数字,程序就会继续询问,直到我们犯错为止。这是因为只要我给它一个介于09之间的数字,它就会继续为True。让我们看一下这个的一个示例输出:

Please enter an integer 0 through 9\. 0
0
Please enter an integer 0 through 9\. 1
1
Please enter an integer 0 through 9\. 2
2
Please enter an integer 0 through 9\. 3
3
Please enter an integer 0 through 9\. 4
4
Please enter an integer 0 through 9\. 39
That's not in range.

请注意,程序一直重复询问同一个问题。有时如果用户不知道如何打破循环,这是没有帮助的。我们可以在我们的陈述中潜在地添加一行,这样它会问问题但提供一个提示。看看编辑后的代码:

ch10_whileLoop3.py

while True:
    num = int(input('Please enter an integer 0 through 9\. Tired? Type a number out of range. '))
    if num in range(0, 10):
        print(num)
    else:
        print('That\'s not in range. ')
        break

正如你所看到的,我们现在告诉用户,如果他们厌倦了提供范围,那么他们可以通过提供范围之外的输入来选择退出。虽然这个例子似乎不是很有用,但想想你可以有多少应用。例如,这种类型的算法可以用于纸牌游戏。你也可以使用类似这样的东西来检查输入是否与现有列表匹配。

现在,让我们再次看一下函数,但现在结合一些我们的循环并添加一些功能。

重新审视函数

如果你还记得第八章Python 简介,我们看过内置函数,但我们也看过如何定义我们自己的函数。现在我们将讨论函数循环中的参数,深入探讨 Python 中控制流的工作方式。

让我们考虑一下涉及范围的问题。范围需要两个参数:最小值和最大值。然而,在 Python 中,我应该注意到你可以只给一个参数,这样就假定你的最小值是0。例如,如果我写range(8),那就相当于range(0, 8)。看看如果你在 Python shell 中输入range(2)会发生什么:

图 10.3 - Python 范围解释与一个参数

图 10.3 - Python 范围解释与一个参数

图 10.3中,你可以看到程序将代码解释为range(0, 2)。但假设你总是在改变你的范围。想想我们之前写的范围算法。我们现在将使用一个函数来重新编写它。这个函数现在也在里面有一个for循环:

ch10_functions1.py

minNum = int(input('What\'s your minimum number? '))
maxNum = int(input('What\'s your maximum number? '))
def myNumbers(minNum, maxNum):
    myList = []
    for num in range(minNum, maxNum + 1):
        myList.append(num)
    print(myList)
myNumbers(minNum, maxNum)

请注意,我们是根据用户输入调用函数的。当此程序运行时,它根据该输入调用函数。我们将在一秒钟内重新访问并通过在算法中调用多个范围来运行程序,但是看看前面的片段给出了什么输出:

What's your minimum number? 3
What's your maximum number? 9
[3, 4, 5, 6, 7, 8, 9]

请注意,我们还调整了for循环中的范围中的最大数字,以包括提供的顶部数字。这样我们就可以得到完整的数字列表。

现在,让我们完全取消输入。我们将使用不同的范围多次调用函数在算法中。看看更新后的片段:

ch10_functions2.py

def myNumbers(minNum, maxNum):
    myList = []
    for num in range(minNum, maxNum + 1):
        myList.append(num)
    print(myList)
myNumbers(4, 9)
myNumbers(1, 3)
myNumbers(9, 17)

最后三个语句是我们调用函数的地方。由于我们定义了函数以接受两个参数,它使用这两个参数来运行函数。因为我们调用了函数三次,所以应该看到三个列表作为输出。让我们看一下:

[4, 5, 6, 7, 8, 9]
[1, 2, 3]
[9, 10, 11, 12, 13, 14, 15, 16, 17]

正如您所看到的,我们在单独的行上打印了每个范围。这是函数可以为我们做的最有用的事情之一。例如,如果我们正在处理图像,我们可以使用库来创建形状,然后定义一个带有改变一些参数的循环的函数。通过一个函数和几个循环,我们可以根据调用函数和使用一些循环在不同位置创建多个具有不同半径的圆。

函数也不限于两个参数。我们可以有多个参数并在函数内定义它们。让我们看一个使用三个参数的函数:

ch10_functions3.py

def menu(appetizer, entree, dessert):
    print('Your appetizer is %s.' %(appetizer))
    print('You entree is %s.' %(entree))
    print('Your dessert is %s.' %(dessert))
menu('street tacos', 'chilaquiles', 'sopapillas')

在这种情况下,我们正在使用已经给定的值调用函数。我喜欢,我的意思是我非常喜欢,墨西哥食物。因此,那个菜单会让我非常开心!这是输出的样子:

Your appetizer is street tacos.
You entree is chilaquiles.
Your dessert is sopapillas.

正如您所看到的,该函数在print语句中使用了每个参数。 “%s”语句用于让程序知道值将被替换的位置。 “%()”语句让程序知道从调用函数中获取哪个值。

现在,让我们看看如果我们想从用户那里获取输入的代码:

ch10_functions4.py

appetizer = input('What would you like as an appetizer? ')
entree = input('What would you like as an entree? ')
dessert = input('what would you like for dessert? ')
def menu(appetizer, entree, dessert):
    print('Your appetizer is %s.' %(appetizer))
    print('You entree is %s.' %(entree))
    print('Your dessert is %s.' %(dessert))
menu(appetizer, entree, dessert)

正如您所看到的,我们的定义和参数是重复的。我们使用input语句从程序用户那里获取信息,然后打印出语句。现在在 Python shell 中看起来是这样的:

图 10.4 - 具有三个参数的函数中的用户输入

图 10.4 - 具有三个参数的函数中的用户输入

正如您所看到的,前三行接受用户输入,然后最后三行将输入合并到函数定义中。这取决于您要创建的内容。例如,如果您要为商店构建在线菜单,您需要用户输入,但您还希望能够确认购买。如果您想要确认借出电子书等在线图书馆,也是如此。我们在算法中经常使用确认语句。查看这些之后,去看看一些您喜欢的网站,看看基于用户输入的确认语句在哪里。您会发现使用非常广泛。函数使我们能够简化该过程。

在我们继续之前,让我们使用几个算法,使用不同的循环和函数提供相同的信息。请记住,我们正在研究迭代,因为控制流意味着顺序。迭代、函数、范围等是我们告诉程序如何响应算法中的指令以及何时重复或继续执行程序的方式。

让我们看一个打印给定用户最大输入的三倍的函数:

ch10_functions5.py

numItem = int(input('What is your maximum number for the list of triples? '))
def cost(numItem):
    while numItem > 0:
        print(numItem * 3)
        numItem -= 1
cost(numItem)

请注意,while循环和函数定义取决于用户输入。然后,程序将打印用户提供的值的三倍,然后减少该数字一次并找到该数字的三倍。让我们看看这意味着什么:

What is your maximum number for the list of triples? 4
12
9
6
3

正如你所看到的,程序找到了4的三倍,即12,然后找到了3的三倍,即9,依此类推。它停止是因为我们告诉它在数字大于0时运行循环。但要记住,我们也可以将它们添加到一个列表中,并且我们可以使用for循环。

让我们看一个类似的程序,它使用了一个函数,但是使用了for循环而不是while循环,并使用了range

ch10_functions6.py

numItem = int(input('What is your maximum number for the list of triples? '))
myList = []
def cost(numItem):
    for x in range(1, numItem + 1):
        newNum = 3 * x
        myList.append(newNum)
    print(myList)    
cost(numItem)

注意我们定义了一些更多的东西,并在算法中添加了一个空列表。虽然代码行数多了一些,但它基本上与先前的代码做了相同的事情。还要注意,我们从1开始范围;否则,它也会在列表中包括0。看一下这个算法的输出:

What is your maximum number for the list of triples? 4
[3, 6, 9, 12]

我们有相同的信息,但是按照从小到大的顺序排列,并且放在一个列表中。从我们的算法中,发生事情的顺序很重要,当然。我们首先收集了用户输入。然后算法定义了一个空列表,然后定义了一个函数。函数然后使用了一个for循环,该循环使用输入创建一个范围,并在数字范围内进行迭代。然后每次迭代都被添加到列表中。最后,算法打印了列表。

正如你所看到的,有多种方法可以获得相同的信息。我们只需要看看哪种方法最适合我们的场景,如何组织信息以便程序可以读取它,并编写一个算法,该算法可以在运行程序时组织信息。

总结

在本章中,我们通过查看for循环、rangewhile循环和函数来讨论控制流和顺序。控制流指的是程序读取算法的顺序。通常在 Python 中,一行代码紧跟着一行代码被读取。在本章中,我们学会了如何控制这个顺序。具体来说,我们学会了可以通过迭代数据来实现这一点。以下是一些重要的要点需要记住:while循环在条件满足的情况下运行,for循环迭代一个序列(字符串、数字、列表、字典、集合或元组),range用于创建一系列数字。

我们还学会了在创建条件、定义函数和设计算法时可以将这些东西结合起来。继续牢记的最重要的事情是顺序很重要,所以我们需要小心地定义必要的变量以及如何编写算法,以便它们不会无限运行或在应该之前就中断。控制流很重要,这样我们的算法才能正常工作而没有错误。

在下一章中,我们将利用迄今为止所学到的知识,完成在多个学科中解决挑战时的计算思维过程。

第十一章:在简单挑战中使用计算思维和 Python

在本章中,我们将再次审视计算思维过程,并将其应用于设计算法,帮助我们解决各种场景和问题。随着本书第二部分应用 Python 和计算思维的结束,我们将结合有关 Python 功能的一些知识与计算思维过程,解决这些问题。

在本章中,我们将涵盖以下主题:

  • 定义问题和 Python

  • 分解问题并使用 Python 功能

  • 概括问题并规划 Python 算法

  • 设计和测试算法

通过本章结束时,您将能够设计最适合所呈现场景的算法。您还将能够确定最符合所呈现问题的 Python 函数,并概括您的解决方案。

技术要求

您需要安装最新版本的 Python 才能运行本章中的代码。

本章中使用的源代码可以在 GitHub 存储库中找到:github.com/PacktPublishing/Applied-Computational-Thinking-with-Python/tree/master/Chapter11

定义问题和 Python

我们将深入探讨我们的第一个场景。您正在为您设计的产品发起一项活动。您正在寻找总额为 100,000 美元的承诺投资。您希望创建一个算法,保存有关为您制作的承诺的信息,同时提供有关每个承诺提供的产品的信息。在编写算法之前,您需要确定一些事项:

  • 您的活动中将运行多少种类型的承诺?

  • 是否会有仅捐款的承诺? 捐款是否会得到任何东西,比如贴纸、电子证书或其他产品?

  • 每个承诺将给予承诺者什么?

所有这些问题都将帮助我们进行规划。但我们还必须考虑其他事项,比如我们从算法中需要什么。它仅仅是列出承诺的数量,还是也将用于与库存进行核对或创建库存? 它会从每个级别可用的一定数量的承诺中倒数吗?每次有人承诺时,它会自动更新吗?

正如您所知,当我们面对问题时,关键是要确定问题的实质。我们需要为问题设定参数,以便提供解决方案。在本节中,我们首先要回答很多这些问题。为了做到这一点,我们需要分解问题,并确定 Python 中哪些功能可以帮助我们解决这个问题。

分解问题并使用 Python 功能

我们可以创建一个用于设计这些承诺的算法。我们将设计一个算法,告诉我们根据一定数量的承诺需要每种类型的商品数量。

例如,假设我们有三个承诺层级和一个仅捐款的承诺。对于最高层,让我们称之为层级 1,您将获得以下内容:

  • 将出售的商品以 20%的折扣翻倍

  • 商品的配件捆绑包,包括四件商品

  • 商品和配件的携带盒

  • 网站的 1 年会员资格

中层,或层级 2,将为您提供以下商品:

  • 以 20%的折扣出售的商品

  • 商品的携带盒

  • 网站的 6 个月会员资格

最低层,或层级 3,将为您提供以下商品:

  • 以 20%的折扣出售的商品

  • 商品的携带盒

假设我们想要一个算法,根据我们允许每个层级的承诺数量,为我们提供我们需要的物品数量。第四层,即仅捐赠的层级,不会影响我们的算法,因为这个算法将仅用于确定我们需要的每种物品的数量,具体取决于承诺的数量。

但事情并不像数字那么简单。假设您需要$100,000 的承诺,就像本章前面提到的那样。那么您需要计算出每个层级的收费,并为它们设定一个价格点。您需要提供足够的承诺,以至少达到$100,000 的目标。

您已经确定您的物品的零售价格为$800。以下是每个物品的成本:

  • $640 是物品本身的价格(给定 20%的折扣,仅适用于第 1、2 和 3 层)

  • $100 的配件(仅适用于第 1 层)

  • $40 的携带箱(适用于第 1、2 和 3 层)

  • 会员费用将以每月$10 的价格传递(假设原始费用为每月$35)

对于第 1 层,承诺需要至少$1,540:

  • 640 × 2 = 1280

  • 12 × 10 = 120

  • 1280 + 120 + 100 + 40 = 1540

对于第 2 层,承诺需要至少$740:

  • 640 + 40 = 680

  • 6 × 10 = 60

  • 680 + 60 = 740

对于第三层,承诺需要至少$680,即 640 + 40 = 680。

现在我们必须弄清楚每个层级将有多少可供购买。但首先,让我们将一些这些数字四舍五入:第 1 层将是$1,600,第 2 层将是$800,第 3 层将是$700。

我们可以编写一个算法来让我们知道每个层级需要多少承诺。但在我们这样做之前,让我们谈谈 Python 和我们可以使用的功能。我们可以首先创建第三层的承诺,使其成为一个父组。然后我们可以创建两个子类,第 1 和第 2 层,它们具有略有不同的特征和成本。我们要问自己的问题是,我们是否需要在算法中详细说明,还是只需添加每个层级的成本/价值。

这是一个很好的问题。答案是取决于您需要这个算法的用途。如果您只是想进行早期简单的计算,并且需要一个可以根据当前销售/承诺定期运行的算法,那么您可以创建一个简单的字典或函数。但是,如果您希望将算法用于多种目的,或者需要能够稍后调整一些成本,那么您可能希望将所有细节编码到算法中。

您为什么需要这个?看一些成功的可用活动。通常,这些活动的原始承诺会售罄。然后新的承诺层会变得可用,您可能希望调整这些层的价格点。例如,承诺可能不再是物品的 20%折扣,而是 15%的折扣。其他变化可能发生,例如由于库存限制而出售装饰品而不是配件等。

您的算法越详细,从长远来看,您可以做的事情就越多。但是,如果您不需要所有这些信息,那么您可能不希望花费太多时间创建算法。

让我们开始处理这个算法。看一下下面显示的片段:

ch11_pledgesA.py

import math
tier1 = 1600
tier2 = 800
tier3 = 700
perc = int(input("What percentage of the 100,000 do you 
    expect will be Tier 1? Type a number between 1 and
    100\. "))
percentage = perc/100
Tier1 = (100000*percentage)/1600
totalTier1 = math.ceil(Tier1)
print("You will need to sell %s Tier 1 pledges if you want 
    %s percent of your sales to be in Tier 1." 
    %(totalTier1,perc))

让我们看一下我们在前面片段中使用的一些东西。我们必须导入math模块,以便我们可以使用math.ceil()数学函数。我们使用这个函数来四舍五入所需的第 1 层承诺的数量。这是因为如果我们向下取整,我们将无法达到所需的百分比。通过这样做,我们找到了我们需要覆盖百分比的最小整数。

此外,我们在我们的print语句中使用了%s占位符来调用我们的字符串,然后说明要使用的字符串。

当我们运行该程序时,输出如下所示:

What percentage of the 100,000 do you expect will be Tier 1? Type a number between 1 and 100\. 45
You will need to sell 29 Tier 1 pledges if you want 45 percent of your sales to be in Tier 1.

请注意,我们的print语句使用了用户输入的百分比,部分是为了确保信息与预期相匹配。要想让 45%的资金来自 Tier-1 承诺,我们至少需要出售 29 个 Tier-1 承诺。如果我们运行数学来验证这一点,我们会发现这个信息是正确的:

正如您所看到的,如果我们出售 29 个 Tier-1 承诺,我们将获得略多于 100,000 的 45%。

但是,假设您还希望算法告诉您基于 Tier-1 承诺数量需要多少物品。让我们看一下调整后的算法:

ch11_pledgesB

import math
tier1 = 1600
tier2 = 800
tier3 = 700
perc = int(input("What percentage of the 100,000 do you 
    expect will be Tier 1? Type a number between 1 and 
    100\. "))
percentage = perc/100
Tier1 = (100000*percentage)/1600
totalTier1 = math.ceil(Tier1)
print("You will need to sell %s Tier 1 pledges if you want 
    %s percent of your sales to be in Tier 1." 
    %(totalTier1,perc))
print("You will need %s items, %s accessories kits, %s 
    carrying cases, and %s year-long memberships." 
    %(totalTier1*2, totalTier1, totalTier1, totalTier1))

请注意,我只添加了一个print语句。以这种方式使用这个算法有利有弊。在这种情况下,我只输出每个层级的数字。我没有在算法中保存需要的物品数量。如果我们想要为将来的参考保存这些信息,我们需要调整如何获取这些信息以及如何在算法中保存它。

对于这个算法,输出如下:

What percentage of the 100,000 do you expect will be Tier 1? Type a number between 1 and 100\. 45
You will need to sell 29 Tier 1 pledges if you want 45 percent of your sales to be in Tier 1.
You will need 58 items, 29 accessories kits, 29 carrying cases, and 29 year-long memberships.

请注意,我们得到了想要的信息。我们将需要58物品29配件套件29携带箱,以及29年度会员资格。同样,如果我们只做一次性的事情,或者我们不希望有任何变化,这将是有帮助的。但让我们明确一点,这几乎从来不是这种情况。我们将希望进行更改。我们还需要根据层级 2 和层级 3 的选择来了解信息。那么我们能做什么呢?

首先,我们需要保存我们的数字。因此,我们需要为物品、配件套件、携带箱和两个会员资格添加一些变量,一个是年度的,一个是 6 个月的。我们还需要决定我们希望其余的承诺如何分配。我们希望其他百分比在层级 2 和 3 之间平均分配吗? 我们希望剩下的三分之一是层级 2,剩下的三分之二是层级 3 吗? 让我们选择这些数字。现在我们的情况是这样的:

  • Tier-1 的百分比是由用户在程序运行时选择的。

  • Tier-2 的百分比将是剩余百分比的三分之一。

  • Tier-3 将是剩余百分比的三分之二。

让我们教给算法。以下文件包含完整的、不间断的代码。我们添加了一些文本来解释某些部分,如下所示:

ch11_pledgesC.py

tier1 = 1600
tier2 = 800
tier3 = 700
perc = int(input("What percentage of the 100,000 do you 
    expect will be Tier 1? Type a number between 
    1 and 100."))
percTier1 = perc/100
percTier2 = (100-perc)/3/100
percTier3 = (100-perc-percTier2)/100

请注意,在下面的片段中,我们添加了一些变量,如totalTier1itemsTier1accTier1cases1。这些变量将帮助我们保存每个层级订购的数量。我们将对层级 2 和 3 做同样的事情:

Tier1 = (100000*percTier1)/1600
totalTier1 = math.ceil(Tier1)
itemsTier1 = totalTier1*2
accTier1 = totalTier1
cases1 = totalTier1
yearMemb = totalTier1
Tier2 = (100000*percTier2)/800
totalTier2 = math.ceil(Tier2)
itemsTier2 = totalTier2
cases2 = totalTier2
sixMemb = totalTier2
Tier3 = (100000*percTier3)/700
totalTier3 = math.ceil(Tier3)
itemsTier3 = totalTier3
cases3 = totalTier3
totalItems = itemsTier1 + itemsTier2 + itemsTier3
totalAccessories = accTier1
totalCases = cases1 + cases2 + cases3
print("You will need to sell %s Tier 1 pledges if you want 
    %s percent of your sales to be in Tier 1." 
    %(totalTier1, perc))
print("You will need %s Tier 2 pledges and %s Tier 3 
    pledges to meet or exceed your $100,000 funding goal." 
    %(totalTier2, totalTier3))

虽然我们还没有打印出总物品或总案例的详细信息,但现在我们已经将它们保存到变量中。现在我们的输出看起来是这样的:

What percentage of the 100,000 do you expect will be Tier 1? Type a number between 1 and 100\. 50
You will need to sell 32 Tier 1 pledges if you want 50 percent of your sales to be in Tier 1.
You will need 21 Tier 2 pledges and 72 Tier 3 pledges to meet or exceed your $100,000 funding goal.

我们应该注意到,我们超过了我们的筹资目标,因为我们一直在四舍五入。也就是说,我们使用$1,540 来代替 Tier 1,我们使用$1,600。对于百分比,我们一直在四舍五入。所有这些加起来将使我们的总额超过 100,000。

让我们再扩展一下算法。以下只是我们已经看到的算法中的新片段,其中包含了我们需要的物品的总数:

print("These percentages are equivalent to %s total items, 
    %s total cases, %s accessories kits, %s year-long 
    memberships, and %s six-month memberships." \
      %(totalItems, totalCases, totalAccessories, 
      yearMemb, sixMemb))

请注意,我们现在可以在我们的print函数中调用我们添加的那些变量,以获取我们库存所需的数量。如果我们没有在算法中定义这些项目,我们将无法获得这些详细信息。

在我们之前的片段中,还有一些项目具有完全相同的价值。然而,我们仍然用不同的变量定义了它们。例如,cases2 = totalTier2sixMemb = totalTier2。尽管两者的值相同,但我们希望将它们分开识别。也许现在这并不重要,但以后也许我们会用完案例。那时我们只想改变案例的价值,而不是 6 个月的会员资格。

因为它们已经分开,我们可以改变一个而不影响另一个。让我们看看新的print语句的输出是什么样的:

These percentages are equivalent to 157 total items, 125 total cases, 32 accessories kits, 32 year-long memberships, and 21 six-month memberships.

看到这一点,你可能会意识到在第一层有一个携带箱,但有两个物品,这就是为什么这两个数字不同的原因。配件和一年的会员只发生在第一层,所以这两个数字相同是有道理的。6 个月的会员只适用于第二层,所以这个数字与第二层的承诺数量相匹配。

当我们进一步考虑这个问题时,我们可能会意识到我们可能希望以不同的方式保存信息。也许我们不是问用户他们想要多少百分比的第一层承诺,而是问他们有多少总物品,然后根据这个来分解层。所有这些都是可能的,所以我们如何定义问题是至关重要的。我们如何保存信息或者从用户那里请求输入也同样重要。分解问题只是创建和设计我们需要的算法过程的一部分。

在这一部分,我们学会了如何解决具有多种解决方案的问题,这取决于我们的目标。当我们定义问题时,我们通常也确定我们需要的变量,并确定根据算法的输出需要什么样的函数是最有用的。除非我们有一个非常简单和直接的问题,否则问题的分解和定义对于成功定义算法至关重要。

现在让我们在下一节中看一下概括问题。

概括问题和规划 Python 算法

在上一节中,我们正在使用一个专为资金筹集活动设计的算法。我们看到的算法是特定于问题的。

现在让我们尝试概括这个问题,并了解我们如何可能设计一个不同的算法。为什么我们需要那个?把它看作一个模板。如果你为初创公司运行多个筹资活动,你可能希望创建一个通用算法,然后根据每个活动的需求进行调整,而不是每次都要重新开始每个活动。

你需要设定一些明确的参数并做出一些决定。为了使这种情况对本书的目的来说更容易处理,我们将限制一下我们的选择:

  • 每个活动将有 3 到 5 个承诺层,不包括仅捐赠。

  • 每个层都会要求每个层所需的物品。

  • 每个层选项都将有一个固定的百分比。

如果有三个层,第一层将占承诺的 50%,第二层将占 30%,第三层将占 20%。如果有四个层,第一层将占承诺的 40%,第二层将占 30%,第三层将占 20%,第四层将占 10%。如果有五个层,第一层将占承诺的 40%,第二层将占 30%,第三层将占 15%,第四层将占 10%,第五层将占 5%。

看一下下面的图表,显示了各层的分解情况:

图 11.1 - 层百分比分解

图 11.1 - 层百分比分解

因为我们使用一个算法来处理许多可能的情况,我们将逐步分解该算法。完整的算法可以在 GitHub 上的ch11_pledgesTemplate.py文件中找到。

在这个第一个片段中,我们要求初始输入,然后保存我们的百分比:

import math
numberTiers = int(input("How many tiers of pledges will you 
    offer? Enter a number between 3 and 5 inclusive. "))
#Number of tiers percentages
if numberTiers == 3:
    tier1 = .50
    tier2 = .30
    tier3 = .20
elif numberTiers == 4:
    tier1 = .40
    tier2 = .30
    tier3 = .20
    tier4 = .10
elif numberTiers == 5:
    tier1 = .40
    tier2 = .30
    tier3 = .15
    tier4 = .10
    tier5 = .05
else:
    print("Please try again and enter the numbers 3, 4,
        or 5\. ")

正如你所看到的,在我们要求输入后有三个条件。请注意,我们将输入转换为整数。这很重要,否则条件将运行,但else条件也将运行。

前面的片段不会给我们任何输出,除了要求输入。因此,在我们的下一个片段之后,我们将展示更多的输出。

重要提示:

请注意此代码中的注释的使用。由于代码的长度,我们将使用注释来找到需要编辑的代码部分。在所有代码中使用注释总是一个好主意,尤其是在冗长的代码中。否则,找到我们想要更改的特定行可能会非常棘手。

一旦我们有了层级的数量,我们将需要知道每个层级中物品的数量。我们需要询问每个层级选择了多少物品。让我们看看前面代码的继续部分:

#Number of items per tier
if numberTiers == 3:
    numTier1Items = int(input("How many items will be 
        provided for a Tier 1 pledge? "))
    numTier2Items = int(input("How many items will be 
        provided for a Tier 2 pledge? "))                 
    numTier3Items = int(input("How many items will be 
        provided for a Tier 3 pledge? "))
elif numberTiers == 4:
    numTier1Items = int(input("How many items will be 
        provided for a Tier 1 pledge? "))
    numTier2Items = int(input("How many items will be 
        provided for a Tier 2 pledge? "))                 
    numTier3Items = int(input("How many items will be 
        provided for a Tier 3 pledge? "))
    numTier4Items = int(input("How many items will be 
        provided for a Tier 4 pledge? "))

请注意,我们只显示了层级为 3 或 4 时的条件。代码文件还将包含 5 个层级的信息,但它遵循了前面代码中显示的内容。请注意,该算法要求输入每个层级。当我们需要处理数字和百分比时,这将是重要的。

此外,我们可以包括一个else语句,如果输入中有错误,它允许我们再次提出问题。如果您愿意,可以将这些类型的条件添加到现有模板中。现在,我们将继续获取用户需要的下一部分信息,例如每个层级的价格点。

现在让我们回想一下我们可能需要什么。我们将需要每个层级的价格点,这也将是模板类型算法的输入请求。由于每个活动的价格点都会有所不同,我们需要让用户输入。输入行将看起来与先前显示的片段非常相似。以下是 3 个层级的情况:

#Price points for each Tier
if numberTiers == 3:
    priceTier1 = int(input("What is the price point of Tier 
        1? Enter dollar amount without dollar sign. "))
    priceTier2 = int(input("What is the price point of Tier 
        2? Enter dollar amount without dollar sign. "))                
    priceTier3 = int(input("What is the price point of Tier 
        3? Enter dollar amount without dollar sign. "))

再次注意,我们使用注释来分隔代码的每个部分。您可以看到,我们正在添加有关每个承诺级别的收费信息。然后代码继续为 3、4 或 5 个层级执行此操作。

正如之前讨论的,您可能还想测试错误或提供在用户添加错误后继续运行代码的替代方法。我们在这段代码中没有处理这些错误,但可以添加以改善用户体验。您可以看到,我们已经开始研究如何将此算法推广到多种情况。

在这种情况下,我们正在为多种用途进行概括。但是在本书中,我们使用了许多算法并看到了许多情景,其中模式的概括要简单得多。概括可以是编写一个变量方程这样简单的事情。或者可以是为多种情况和条件创建算法。这就是为什么确定我们的问题是什么以及我们确切想要实现什么是很重要的。

在本节中,我们看了如何何时从用户那里获取输入。我们还通过定义变量来存储输入并在我们的方程式中使用它,以便算法输出必要的信息。

为了完成算法,我们将进入下一部分,重点是设计和测试算法。

设计和测试算法

在上一部分中,我们从用户那里得到了很多输入,因为我们正在创建一个用作许多活动模板的算法。现在我们需要以各种方式使用这些输入。到目前为止,我们已经有了这些:

  • 承诺层级的数量

  • 不同层级的百分比分布

  • 每个层级的物品数量

  • 每个层级的成本

现在我们可以利用所有这些信息做一些事情。首先,让我们谈谈我们可能想要的东西。我们可能想测试出售特定数量的层级可以赚多少钱。我们还可以根据筹款目标来分解我们需要多少个每个层级,就像本章的第一部分所做的那样。

什么会最有帮助? 嗯,这取决于你的需求。我要说的是,我想根据筹资目标来分解。我想知道我需要提供每种承诺类型的数量。所以现在我必须想办法从我已经定义的变量中获取这些信息。

我们也需要在这里有三个条件。因为每个等级类型的变量和数量都不同,我们需要确保我们考虑到这些信息。首先让我们考虑三个等级。根据筹资目标,以下是一些有用的输出:

  • 要提供的每个等级的承诺数量

  • 每个等级需要存货的物品数量

那我们该如何计算呢?

假设我们的筹资目标是 50,000 美元,假设一级的成本是 500 美元。那么,我们可以采取以下步骤来找到需要的一级承诺数量:

  1. 将筹资目标乘以百分比,即,50,000 × 0.50 = 25,000。

  2. 将得到的数字除以承诺的成本,即,25,000 ÷ 500 = 50。

这意味着我们需要发布 50 个一级承诺。现在假设用户输入了一级有3个物品。那么,这意味着 50 × 3 = 150 个物品。

现在让我们在我们的代码中看看。记住,这是与之前片段相同的文件(ch11_pledgesTemplate.py)。我们将继续讨论并使用代码的部分:

#Breakdown of number of Tiers based on funding goal
fundGoal = int(input("What is the funding goal for this 
    campaign? Enter dollar amount with no symbols. "))
if numberTiers == 3:
    Tier1Total = math.ceil(fundGoal*tier1/priceTier1)
    Tier2Total = math.ceil(fundGoal*tier2/priceTier2)
    Tier3Total = math.ceil(fundGoal*tier3/priceTier3)
    print("For a funding goal of %s with %s tiers, you'll 
        need %s Tier 1 pledges, %s Tier 2 pledges, and %s 
        Tier 3 pledges. " % (fundGoal, numberTiers, 
        Tier1Total, Tier2Total, Tier3Total))

在前面的片段中,我们有一个print函数,其中包含每个等级的承诺数量,但它们也保存为我们条件语句中的函数。请注意,我们现在将在这里得到一些输出。我们将从这段代码中得到我们需要的承诺数量,但不是每个等级的物品数量。我们很快会添加这部分内容。现在,当我们运行程序时,输出看起来是这样的:

How many tiers of pledges will you offer? Enter a number between 3 and 5 inclusive. 3
How many items will be provided for a Tier 1 pledge? Enter a number between 1 and 3 inclusive. 3
How many items will be provided for a Tier 2 pledge? Enter a number between 1 and 3 inclusive. 2
How many items will be provided for a Tier 3 pledge? Enter a number between 1 and 3 inclusive. 1
What is the price point of Tier 1? Enter dollar amount without dollar sign. 500
What is the price point of Tier 2? Enter dollar amount without dollar sign. 400
What is the price point of Tier 3? Enter dollar amount without dollar sign. 350
What is the funding goal for this campaign? Enter dollar amount with no symbols. 50000
For a funding goal of 50000 with 3 tiers, you'll need 50 Tier 1 pledges, 38 Tier 2 pledges, and 29 Tier 3 pledges.

正如你所看到的,我们现在知道我们需要列出 50 个价值 500 美元的一级承诺,38 个价值 400 美元的二级承诺,以及 29 个价值 350 美元的三级承诺,以达到我们的筹资目标。现在我们必须根据每个等级提供的物品数量来计算每个等级需要多少物品。代码如下:

if numberTiers == 3:
    Tier1Total = math.ceil(fundGoal*tier1/priceTier1)
    Tier2Total = math.ceil(fundGoal*tier2/priceTier2)
    Tier3Total = math.ceil(fundGoal*tier3/priceTier3)
    print("For a funding goal of %s with %s tiers, you'll 
        need %s Tier 1 pledges, %s Tier 2 pledges, and %s 
        Tier 3 pledges. " % (fundGoal, numberTiers, 
        Tier1Total, Tier2Total, Tier3Total))
    Tier1Items = numTier1Items*Tier1Total
    Tier2Items = numTier2Items*Tier2Total
    Tier3Items = numTier3Items*Tier3Total
    print("For %s Tier 1 pledges, you'll need %s items. For 
        %s Tier 2 pledges, you'll need %s items. For %s 
        Tier 3 pledges, you'll need %s items. " 
        %(Tier1Total, Tier1Items, Tier2Total, Tier2Items, 
        Tier3Total, Tier3Items))

正如你所看到的,现在我们有另外三个数学方程和一个print语句,为我们分解了信息。我们将得到每个等级的承诺数量,以及每个等级所需的物品数量。如果你想从这个模板中获得更多信息,你可以包括本章第一个示例中的一些内容,那里我们分解了每个承诺的物品类型。我们将把这个挑战留给你。

现在,对于三个等级和 50,000 美元的筹资目标,我们的最终输出如下:

How many tiers of pledges will you offer? Enter a number between 3 and 5 inclusive. 3
How many items will be provided for a Tier 1 pledge? 3
How many items will be provided for a Tier 2 pledge? 2
How many items will be provided for a Tier 3 pledge? 1
What is the price point of Tier 1? Enter dollar amount without dollar sign. 500
What is the price point of Tier 2? Enter dollar amount without dollar sign. 400
What is the price point of Tier 3? Enter dollar amount without dollar sign. 350
What is the funding goal for this campaign? Enter dollar amount with no symbols. 50000
For a funding goal of 50000 with 3 tiers, you'll need 50 Tier 1 pledges, 38 Tier 2 pledges, and 29 Tier 3 pledges. 
For 50 Tier 1 pledges, you'll need 150 items. For 38 Tier 2 pledges, you'll need 76 items. For 29 Tier 3 pledges, you'll need 29 items.

正如你所看到的,我们不仅得到了我们需要的信息,而且还设置了变量,以便在需要时使用这些信息。回想一下我们之前讨论过的章节和笔记,让我们试着确定我们现在如何保存这些信息。

首先想到的是,我们可以创建一个字典来为我们存储信息。如果我们这样做,那么我们可以从那个字典中调用我们需要的信息,比如一个等级的物品数量。如果需要,我们还可以调整键值对,而不必重新输入整个内容。假设我们一开始的一级成本是 500 美元,但现在我们需要它是 600 美元,而其他等级不会改变。那么我们只需调整那一个值。

这种情况可以让你探索我们讨论过的 Python 编程语言的许多功能。花些时间研究代码,然后进行一些调整,尝试根据不同的条件利用你的知识来改进它。

记住,我们总是面临可以用不同方式解释的问题情况。我们需要编写满足我们自己和客户需求的算法。有时,我们会直接从利益相关者那里得到澄清。其他时候,我们需要要求澄清和/或自己做一些假设。关键是我们要设计算法并记录我们的进展,这样我们就可以在不得到所需内容时调整、适应和更改我们的工作部分,而不必重新开始。

总结

在本章中,我们通过处理更复杂的情景和对该情景的解释,再次学习了计算思维过程。我们学会了如何分解所提供的问题,然后识别模式、概括它们,并设计算法。我们利用了书中学到的一些知识,编写了一个提供我们所需信息的算法。

计算思维过程帮助我们培养技能,使我们的算法规划变得更容易。通过走过这个过程,我们更多地了解了 Python 的能力和函数在特定情况下如何帮助我们。我们还学会了如何概括模式,有时是为了解决问题而创建简单的方程,但有时是创建可以帮助我们在多种情况下的算法,而不必每次重新创建它们。随着我们对 Python 的了解越来越多,在第二部分应用 Python 和计算思维的最后一章中,我们对计算思维过程更加熟悉。

第三部分使用计算思维和 Python 进行数据处理、分析和应用中,我们将继续探讨 Python 的其他能力,以处理数据处理、分析和应用计算思维元素。在下一章中,我们将开始研究数据以及如何使用 Python 分析数据、创建可视化表示,并编写与实验数据配合的算法。

第三部分:使用计算思维和 Python 进行数据处理、分析和应用

数据无处不在。我们使用数据来决策政策,比如为学区提供多少资源,县或州的医疗保险计划预算有多大,我们在某个地区的住房费用是多少,以及房地产市场的趋势。数据也嵌入在我们与广告互动的方式中。简而言之,了解更多关于数据以及如何使用 Python 分析这些数据是一项非常重要的技能。

在本节中,您将了解与数据分析和其他应用相关的 Python 编程语言的高级功能,如加密、实验数据、数据挖掘和机器学习。我们将使用计算思维为许多现实世界的应用问题设计解决方案和算法,从语言和历史分析到一些机器学习应用。

本节包括以下章节:

  • [第十二章],在实验和数据分析问题中使用 Python

  • [第十三章],使用分类和聚类

  • [第十四章],在统计分析中使用计算思维和 Python

  • [第十五章],应用计算思维问题

  • [第十六章],高级应用计算思维问题

第十二章:在实验和数据分析问题中使用 Python

在本章中,我们将看看 Python 如何帮助我们使用专门用于数据分析和数据科学的算法和库来理解和分析数据。我们将首先介绍实验数据,然后转向使用两个主要库NumPypandas的算法。

在本章中,我们将涵盖以下主题:

  • 定义实验数据

  • 在 Python 中使用数据库

  • 使用 Python 进行数据分析

  • 使用额外的库进行绘图和分析

在本章结束时,您将能够定义实验类型、数据收集以及计算思维在设计模型和解决方案时的帮助。您还将学习如何使用数据库,特别是NumPypandasMatplotlib,来帮助分析和显示数据。最后,您将能够设计算法,以帮助数据分析,以便从现有数据中学习。

技术要求

您需要安装最新版本的 Python 来运行本章中的代码。

您还需要安装一些库,包括NumPypandasMatplotlibSeaborn

您可以选择使用集成环境来运行 Python,例如Anaconda,它可以简化库依赖关系,并有助于在笔记本中组织您的算法。

本章中使用的源代码可以在 GitHub 存储库中找到:github.com/PacktPublishing/Applied-Computational-Thinking-with-Python/tree/master/Chapter12

定义实验数据

我们终于到了本书的数据章节我们都有自己的偏见和喜欢生活的领域。这是我的其中之一。数据如此重要的原因有很多,但让我们从这样一个事实开始,即数据、我们如何收集它、如何分析它以及如何呈现它对我们的日常生活有巨大影响。

在编写算法以显示信息时,我们有责任以尽可能少的偏见方式分享数据,确保我们的数据包容和代表我们的社区和人民。在我们深入讨论这个话题之前,我想确保我说过这些话。对我来说,这就是我如何爱上代码和 Python 的方式。

在本节中,我们将讨论实验数据,定义它是什么,以及在处理实验数据时使用的关键术语。

现在,让我们开始吧。实验数据是一个从科学和工程中得到应用的术语。然而,我们也在其他领域使用实验数据,比如教育、公民事务等。实验数据包括方法论,我们如何测量数据,我们正在进行的实验的设计,以及我们如何呈现我们从这些设计中收集和分析的数据。在本章中,我们不是在设计实验。我们将专注于如何使用 Python 来分析现有数据。但是重要的是要注意,如果我们对实验有发言权,我们需要确保公平地设计它。

如果实验设计和数据收集方法是合理的,那么我们使用和分析的数据将比如果我们从一个有偏见的实验开始要更有帮助。虽然我们永远无法完全消除偏见,但作为研究人员和开发人员,我们有责任以尽可能少的偏见方式呈现数据。为什么这很重要?想想所有基于数据、图表和实验呈现的信息而发生的政策变化。如果实验从一开始就有偏见,结果将导致可能无法充分包容社区需求的政策,例如。

在处理数据时,我们总是在使用计算思维元素。在解决问题时,我们必须定义问题是什么,我们想要研究什么,我们想要如何衡量它,我们将如何能够创建和推广模式,以及我们需要使用什么算法来产生数据的最佳表示。我们在数据分析中所做的一切都受益于我们使用计算思维元素。

数据科学是STEM领域中不断增长的领域。2017 年,它被称为美国增长最快的领域。美国劳工统计局表示,预计到 2026 年,数据科学和统计领域将新增 1150 万个新工作岗位。目前,可用的工作岗位比合格的候选人更多。

现在,让我们看看 Python 如何让我们处理数据。

在实验数据中,我们希望使用自变量、因变量和控制变量收集信息:

  • 自变量是由研究人员改变或控制的变量。

  • 因变量是由研究人员测量或测试的变量;因变量依赖于自变量。

  • 控制变量是实验中必须保持不变的变量或因素。

让我们看一个实验中这些变量的简单例子。一个常见的例子涉及植物生长的研究。自变量可以是一个或多个变量。对于我们的例子,我们的自变量将是我们添加到植物中的肥料量。植物生长将是一个因变量。对照组将不会得到任何肥料,只有水。因此,在我们的实验中,假设我们有五棵植物要测量。一棵植物只会得到水。那就是我们的对照组。其他四棵将添加不同水平的肥料。那些是我们的实验植物。生长是依赖于肥料的数量的。

设计实验时,我们希望有三个条件成立:可靠性、有效性和可推广性。以下是这些条件的含义:

  • 可靠性与测量的一致性有关。这意味着如果我们模仿条件,我们的结果应该是可靠地相似的。

  • 有效性与实验是否测量了其意图测量的内容有关。

  • 可推广性与结果是否能够推广并应用到其他环境有关。

关于实验,我们还可以深入了解更多细节和深度,但在本章中,我们只关注在获得数据后会发生什么。了解这些基本术语对于我们参与实验设计是很重要的。作为开发人员,根据我们的角色,可能会出现这种情况。例如,在初创公司中,每个人可能都参与产品开发的各个方面。因此,在这些情况下,我们可能会参与实验以及随后的分析算法。

现在,让我们继续讨论数据分析可以做什么,以及 Python 库如何帮助我们实现分析实验结果所需的内容。

使用 Python 中的数据库

在这一部分,我们将看一些可以与 Python 编程语言一起使用的库和包。我们有时使用这两个术语来交替使用,但为了清晰起见,一个包包含模块,而一个库是包的集合。

我们使用 Python 库就像使用内置的模块一样,比如我们在第四章中首次使用的math模块,理解逻辑推理。在我们的源代码中,我们在创建算法之前使用import math导入了math模块。在第四章应用归纳推理部分的示例中,我们使用了该模块的math.floor()函数,这使我们能够将一个数字向下舍入,而不管其小数值是多少。当我们在 Python 中导入模块或库时,我们正在利用额外的功能和能力,使我们能够更深入地使用编程语言。

那么,什么是库?在 Python 中,指的是可以重复使用的代码块。库包含一系列模块。Python 有许多可用的库,就像 Python 本身一样,许多库都是开源的,这意味着任何人都可以下载和使用它们。因为我们将在本章中处理数据,所以现在我们将继续使用 pandas、NumPy 和 Matplotlib。然而,还有许多其他库和许多类型的库。例如,有图形用户界面GUI)框架,如KivytkinterPyQtPySimpleGUI等。对于游戏,还有其他库,如PygamePyglet。在机器学习中,TensorFlow库是由GoogleBrain团队合作开发的一种流行工具。但这只是一些可用库和使用它们的领域的例子。

安装库

虽然math模块是内置在 Python 语言中的,但库需要安装。在Python 3.9中,我们用来安装库的程序是pip 安装程序。这是一个内置在 Python 中并从命令提示符窗口运行的命令。我在这里要提到的一个警告是,权限和我们安装 Python 的位置很重要,所以如果你的计算机属于你的雇主,请确保 Python 程序路径已根据需要进行调整,以便你可以访问所有模块并安装库。权限可能会有所不同。

在我的情况下,虽然我的主要计算机不属于我,但我有管理员权限,所以我可以以管理员身份运行我的命令提示符,并从那里运行pip

下面的截图显示了安装sympy Python 库。如你所见,使用pip install sympy命令将库安装到我们的系统上。值得一提的是,sympy是 Python 可用的符号数学库。由于我已经安装了我们将要使用的其他库,我必须展示一个我还没有在我的计算机上安装的包的安装:

图 12.1 – 安装 Python 库

图 12.1 – 安装 Python 库

如果你试图安装一个你已经安装过的库,例如如果我再次尝试安装 pandas,你会收到一个已满足要求的消息,就像下面截图中显示的那样。请注意,用户信息将被填入你的用户,而不是像截图中显示的那样被涂黑,这也显示了库包在你的系统上的位置:

图 12.2 – 已满足要求的消息

图 12.2 – 已满足要求的消息

你可能还希望使用 Anaconda,这是 Python 和 R 编程语言的开源发行版。安装了 Anaconda 后,你可以使用CMD.exe提示窗口使用conda installpip install来安装你的库。Anaconda 导航器中包含的 Jupyter 笔记本可以运行和保存你的 Python 程序。

Anaconda 发行版程序中包含了更多的程序和包,它们可以简化我们与 Python 编程语言的交互方式。如果你进行大量编码,这是一个很好的资源。

如果你还没有这样做,现在是一个安装 NumPy、pandas 和 Matplotlib 库的好时机,在我们开始使用它们来分析和显示数据以及创建模型之前。

使用 NumPy 和 pandas

NumPy,就像 Python 中的许多库和 Python 编程语言本身一样,是一个开源库。NumPy 用于多维数组和矩阵数据结构。Python 本身没有数组;它有列表。因此,库可以用来为我们的算法提供这种能力。当我们有多个相同类型的元素时,我们可以使用数据结构来保存它们 - 也就是数组

pandas 库用于分析数据,构建在 numpy 包的基础上。pandas 和 NumPy 库经常一起使用。当我们需要图形模型时,我们会添加第三个库,Matplotlib,或者其他类似的库。在本章中,我们将继续使用 Matplotlib。

当我们导入库时,我们可以将它们作为整个名称导入,比如在这种情况下是numpy,或者我们可以缩短它们以方便使用。如果我们想要导入库,我们可以使用import numpy。假设我们想要创建一个从011的数字数组。我们可以使用numpy通过组合arangereshape函数来组织它。让我们看一下代码片段:

ch12_abbreviate.py

import numpy as np
myArray = np.arange(0, 12).reshape(2, 6)
print(myArray)

请注意,我们导入的是numpy as np,而不是只有numpy。这意味着我现在可以使用np来调用 NumPy 函数,而不必每次都输入numpy

提示:

请注意,np是 NumPy 的标准缩写,所以你可能经常看到它。你可以将 NumPy 导入为任何名称,但np是标准约定。

在上面的代码片段中,我们要求算法将从011的数字列表分成2行,每行6个元素。然后,我们打印数组以查看我们的结果。看一下输出:

[[ 0  1  2  3  4  5]
 [ 6  7  8  9 10 11]]

正如你所看到的,我们从0开始,这是我们在范围012中的下限。现在我们有两行,每行有六个数字。我们不包括 12。如果我们尝试做013,我们将无法重塑我们的数组,因为我们无法均匀地分割 13 个数字。我们将得到以下回溯错误(最近的调用最后):

  File "C:/Users/… /ch12_abbreviate.py", line 3, in <module>
    myArray = np.arange(0, 13).reshape(2, 6)
ValueError: cannot reshape array of size 13 into shape (2,6)

请注意,当你看到省略号()时,你的信息将会不同。它将根据你的 Python 设置有你的文件位置或路径。例如,你的文件位置可能类似于C:/Users/JohnSmith/Documents/ch12_abbreviate.py。这应该替换前面代码中的文件位置。

正如ValueError消息所示,我们无法将数组重塑为(2, 6)形状,因为我们会有一个多余的数字。

现在,让我们来看一个 pandas 的DataFrame。我们使用DataFrame来以行和列的方式操作我们的数据。让我们看一个例子:

ch12_pdDataFrame.py

import pandas as pd
myAddressBook = {'names': ['Susan', 'Malena', 'Isabel',    
                           'Juan', 'Mike'],
                 'phoneNumbers':['333-333-3333', 
                                 '444-444-4444', 
                                 '555-555-5555',
                                 '777-777-7777', 
                                 '888-888-8888']}
addressBook = pd.DataFrame(myAddressBook)
print(addressBook)

请注意,我们创建了一个带有联系人姓名和号码值对的字典。这样做之后,我们将我们的通讯录保存为DataFrame,它将以表格形式组织我们的信息。最后,我们打印了我们的通讯录。看一下输出:

    names  phoneNumbers
0   Susan  333-333-3333
1  Malena  444-444-4444
2  Isabel  555-555-5555
3    Juan  777-777-7777
4    Mike  888-888-8888

请注意,pandas 使用字典中的信息来为我们的列创建标签。我们没有为行标签提供信息,所以 Python 和 pandas 自动使用了04的数字。该算法生成了一个表格,提供了我们通讯录中的姓名和号码。

正如你所看到的,pandas 和 NumPy 只是为 Python 增加了更多的功能。在我们转向 Matplotlib 之前,请注意我们还没有真正看到实际的数据分析。我们很快就会看到。现在,我们知道如何使用我们的库,以及我们可以使用它们来组织和分析数据。让我们简要地谈一下 Matplotlib,然后转向一个例子,我们可以使用一个数据文件来进行一些分析。

使用 Matplotlib

正如我们在前一节中提到的,pandas 和 NumPy 并不提供我们数据的可视表示或可视模型。为了从我们的数据创建图表,我们可以使用 Python 中的 Matplotlib 库。就像我们将 pandas 和 NumPy 库分别导入为pdnp一样,我们也可以缩短 Matplotlib。完整的库包含多个模块。我们将经常使用pyplot模块。Matplotlib.pyplot的最常用的缩写是plt。如果我们在调用 Matplotlib,我们通常将其缩写为mlt。让我们快速看一个示例:

ch12_matplotlib.py

import matplotlib.pyplot as plt
plt.plot([0, 3, 6], [4, 5, 6])
plt.show()

请注意,这个特定的库的名字甚至比matplotlib还要长。要导入这个库,我们必须使用matplotlib.pyplot来导入它。想象一下,每次在算法中需要它时都要写这么长的名字。不,谢谢! 相反,我们将它导入为plt

现在,让我们看一下代码片段中的第二行。我们正在创建一个图表,将第一个列表中的每个数字与第二个列表中的数字匹配。因此,我们有三个坐标对:(0, 4)(3, 5)(6, 6)。使用这段代码时的输出不仅仅是一行输出;它是一个图表。现在,算法在前面代码片段的第二行创建了该图表。

但除非你告诉它你想要的,否则它不会向你显示。把show()函数想象成 Python 中的print()函数。除非我们告诉算法我们想要看到什么,否则算法会在后台执行任务但不会向我们显示。以下图表显示了这个算法的输出:

图 12.3 – Matplotlib 示例图

图 12.3 – Matplotlib 示例图

现在,正如你所看到的,图表本身已经很有帮助。我们可以看到这是由这些点定义的线性关系。但请注意屏幕底部给出了一些选项。左下角的主页图标可以在任何时候将您的图表重置为其原始窗口。缩放功能,如下面的截图所示,允许我们选择我们想要更仔细查看的图表部分:

图 12.4 – Matplotlib 的缩放功能

图 12.4 – Matplotlib 的缩放功能

请注意,左侧的图表显示了我们选择的我们想要仔细观察的图表部分。然后,右侧的图表只显示了我们选择的值。Matplotlib 还允许我们配置子图,并在多个表示之间使用箭头来导航。例如,如果我在放大后点击图表上的左箭头,它会将我带回到先前的表示。点击Home按钮会将我带回到原始图表。

当然,这并不是 Matplotlib 允许的唯一类型的图表或表示。这只是对可视化表示的一个小小的窥视。

提示:

有关可用图表类型的其他资源可以在这里找到:matplotlib.org/3.3.2/tutorials/introductory/sample_plots.html

在本节中,我们学习了 Matplotlib 库。我们学习了如何创建一个简单的图表,以及在创建图形时如何从输出中放大我们图表的部分。

现在我们知道如何访问我们的库以及每个库可以为我们做什么,让我们看看如何使用它们来分析数据。

了解 Python 数据分析

在前一节中,我们介绍了一些可以用来分析 Python 数据的库。在本节中,我们将看一个例子和多个代码片段,使用真实数据和 Matplotlib 构建条形图,但在此之前,让我们回顾一下为什么 Python 在数据分析方面如此重要。

由于 Python 是面向对象的,它允许我们简化非常复杂和/或大型的数据集。这使得数据的可读性很高,并且使用库可以产生数据表示,如表格和可视模型,使我们能够预测数据的走向,创建回归分析等。正如本章介绍中提到的,数据分析对于决策也是至关重要的。一个设计良好的实验产生的数据是可靠的,并且是可推广的。数据分析可以成为我们社会更加平等和公平的工具。

说了这么多,我们将更多地关注我们可以用 Python 做什么的机械方面,而不是我们如何解释它,以便我们可以了解 Python 如何进行数据分析,并使用我们的库呈现结果。话虽如此,我们将在第十五章中使用更多的例子,应用计算思维问题,以及第十六章中,高级应用计算思维问题,在各个领域使用一些这些工具,这不仅将提供我们使用这些工具的机会,还将提供我们探索数据分析在这些样本背景下的意义。

现在是时候看一个例子,这个例子可以帮助我们进一步了解这些工具的能力,以及我们如何编写算法来解决一些提出的问题。

在开始之前,我们将使用一个数据文件ch12_data.csv,它可以在 GitHub 存储库中找到。该文件包含了 1996 年至 2012 年按种族/族裔、完成时间、性别、机构控制和接受申请百分比对求学学生的毕业率。这些数据是从国家教育统计中心下载的:nces.ed.gov/programs/digest/d19/tables/dt19_326.10.asp

我们存储库中的数据文件只包含所有四年制机构的数据,而不是整个文件。一些标题也被简化了。

当我们想要使用数据文件时,重要的是告诉 Python 它可以在哪里找到它,以便在运行算法时知道要使用什么。为此,我们使用os模块,它允许我们的算法与我们的操作系统进行交互。请注意,我们的代码片段已经包括了其他库(我们稍后会使用它们):

ch12_csvAnalysis.py

import pandas as pd
import matplotlib as mlp
import matplotlib.pyplot as plt
import os
#Let Python know correct directory for file.
os.chdir('C:\\Users\\...\\Program Files\\Chapter 12')
print('Current directory', os.getcwd())

与以前一样,请确保用您自己的位置替换用户信息中的省略号。在存储库中提供的算法中,您还需要调整该位置,以便运行此算法,因为该算法的位置将是我的路径。

现在我们已经告诉 Python 在哪里找到文件,我们可以在算法中使用它。如果我们运行这个算法,我们会看到输出应该与我们在以os.chdir开头的行中注意到的路径匹配:

Current directory C:\Users\...\Program Files\Chapter 12

再次注意,我们的路径不会匹配。这将取决于您保存文件的位置。

下图显示了与我们的.csv文件相同的数据和文件,以.xls 格式,因为这样更容易指出我们需要的内容。请注意,我们将使用.csv文件进行分析:

图 12.5 - 以.xls 文件格式化为 Python 使用的数据

图 12.5 - 以.xls 文件格式化为 Python 使用的数据

如果我们只想提取所示的行,我们可以使用以下代码片段从我们的.csv文件中获取该信息:

ch12_csvAnalysis_2.py

import pandas as pd
graduates = pd.read_csv('ch12_data.csv', index_col = 0)
print(graduates[0: 16])

请注意,我们的算法导入了 pandas 库。read_csv()函数告诉 Python 我们将使用文件名和我们想要开始处理的列的索引。该索引给我们提供了我们将用作行标题的值。如果索引不在第一列,我们可以将其更改为不同的值。然后,我们打印我们想要看到的行。因为我们的数据很宽,我们的输出如下所示:

图 12.6 - ch12_csvAnalysis.py 算法的输出

图 12.6 - ch12_csvAnalysis.py 算法的输出

请注意,我们只看到了前两列,然后有省略号。这向我们表明第二列和第三列之间还有更多的列。这并不意味着 Python 没有读取其余的信息,只是对我们不可见。最后的输出行[13 rows x 10 columns]实际上告诉我们我们提取的数据中有多少行和列。

假设我想要看到一个组的数据,比如Hispanic,并使用 1996 年至 2012 年之间的条形图进行比较。然后,我可以使用 Matplotlib 库中的条形图绘制。让我们看看对以下算法所做的调整:

ch12_csvAnalysis_3.py

import pandas as pd
import matplotlib as mlp
import matplotlib.pyplot as plt
import os
#Let Python know correct directory for file.
os.chdir('C:\\Users\\sofia.dejesus\\OneDrive - Hawken 
    School\\03_book\\Program Files\\Chapter 12')
graduates = pd.read_csv('ch12_data.csv', index_col = 0)
print(graduates[0:13])
fig, ax = plt.subplots()
ax.bar(graduates.index, graduates['Hispanic'])
ax.set_xticks(graduates.index)
ax.set_xticklabels(graduates.index, rotation = 60, 
    horizontalalignment = 'right')
ax.set_title('Number of Hispanic graduates from 1996-2012', 
    fontsize = 16)
ax.set_ylabel('Number of Graduates')
plt.show()

我们应该仔细查看我们前面的代码中的一些事情。我们做的第一件事是添加fig, ax = plt.subplots()行。这行允许我们创建图表,还允许我们在一个图中创建多个图表。如果我们要添加四个图表,我们会使用fig, ax = plt.subplots(2, 2),这会告诉算法我们要在两行两列中创建四个图表。如果我们像前面的代码中那样将括号留空,我们就只有一个子图。

接下来,我们要确定我们将创建的图形,这是一个条形图。我们只想比较西班牙裔人口的数字,所以我们在ax.bar(graduates.index, graduates['Hispanic'])行中确定了这一点。

我在这里应该指出,许多开发人员使用dataframe作为他们的变量。我更喜欢用描述性的方式命名我的DataFrame,所以我没有将我的DataFrame称为df,而是在算法中将其称为graduates。无论你的偏好是什么,这就是我们目前正在使用的DataFrame,用来创建可读的图表。

请注意代码中的刻度和刻度标签;我们首先确定我们将从哪里获取刻度的数据以及标签将是什么。然后,我们可以通过添加旋转(如果我们想要倾斜标签)、对齐等来为我们的图表添加更多格式。我们还可以在这里更改字体大小。最后,在显示图表之前,我们设置y轴标题和条形图标题。以下截图显示了这个算法的图表:

图 12.7 - ch12_csvAnalysis_3.py 的西班牙裔毕业生情况

图 12.7 - ch12_csvAnalysis_3.py 的西班牙裔毕业生情况请注意,我们的图表有标签,清晰显示了数据,我们能够修改格式,使其可读。在分析信息时,我们还注意到了第一年和 2000 年之间,以及 2000 年和 2002 年之间的间隔。这些年份的数据没有包含在数据文件中。

这只是使用 Matplotlib 可能的图表之一。我们将有机会在第十五章中探索更多内容,应用计算思维问题,以及第十六章高级应用计算思维问题,这两章专门讨论了在本书前几章中讨论的各种领域的样本。现在,让我们继续探讨数据和 Python 库的其他应用。

使用额外的库进行绘图和分析

在结束本章关于实验数据、库的使用以及绘图和分析数据之前,让我们看看另外三个在数据分析和绘图中有帮助的库。这些不是唯一用于分析和绘图的库,也不会是我们在本书的其余部分中探索的唯一库:

  • Seaborn是用于数据可视化的库;建立在 Matplotlib 之上。
  • SciPy是用于线性代数、优化、统计等的库;建立在 NumPy 之上。
  • Scikit-Learn是用于机器学习的库;是 SciPy 堆栈的一部分。

在接下来的章节中,当我们解决一些需要使用这些库的应用问题时,我们将更深入地使用其中一些库。现在,让我们快速看一下这些库在查看数据集时可以帮助我们解决什么问题。

使用 Seaborn 库

Seaborn 库为我们提供了更多的功能,除了 Matplotlib 的可视化功能。我们可以用 Seaborn 库做很多事情,通常我们将其简化为sns进行导入。以下是该库的一些常见用途:

  • 相关性
  • 聚合统计(对分类值的观察)
  • 依赖变量的线性回归图
  • 创建具有多个图表的抽象和网格

Seaborn 最棒的一点是它也能很好地与 pandas 一起使用。当与 pandas 的数据框结合时,创建数据的统计表示和可视化变得很容易。

Seaborn 有一些可以访问的示例数据集 - 也就是说,它们是内置的 - 只要我们知道数据集的名称,就可以调用内置数据集。然后我们可以用一行简单的代码调用内置数据集。

让我们看看以下代码片段和生成的图形:ch12_seabornSample.py

# Import seaborn
import seaborn as sns
sns.set_style('darkgrid')
# Load an example dataset
exercise = sns.load_dataset("exercise")
#Create the plot
sns.relplot(    
    data=exercise,    
    x='id', y="pulse", col="time",
)

从上面的代码中,您可以看到我们为图表设置了样式。我们在导入库后为我们的图表添加了'darkgrid'样式。Seaborn 有一些内置样式:whitewhitegriddarkdarkgridticks

以下截图显示了 Seaborn 的结果图:

图 12.8 - 使用 darkgrid 样式的练习样本数据集的图表

图 12.8 - 使用 darkgrid 样式的练习样本数据集的图表

从图表中可以看出,我们可以分析脉搏和时间或其他变量之间是否存在相关性。

我们还可以使用成对绘图来显示变量之间是否存在相关性。让我们使用另一个内置数据集 flights 来看看成对绘图的效果:

ch12_pairplotSNS.py

# Import seaborn
import seaborn as sns
sns.set_style('darkgrid')
# Load an example dataset
flights = sns.load_dataset("flights")
#Create the plot
sns.pairplot(
    data=flights,
)

上述代码片段与之前的练习数据非常相似。不同之处在于,在这种情况下我们调用了pairplot()函数。以下截图显示了我们得到的图表网格:

图 12.9 - 使用 Seaborn 进行成对绘图

图 12.9 - 使用 Seaborn 进行成对绘图

请注意,乘客数量和年份似乎呈正相关。也就是说,后来的年份比飞行初期的年份有更多的乘客。在分析这些数据集时,我们可能想要进行预测,比如说现代时代会有更多的乘客飞行。

我们可以使用图表来帮助我们进行预测。也就是说,这些数据相当古老,所以我们需要更多更新的数据来进行准确的预测。数据越多,越好。对于大量数据,我们还可以使用机器学习,我们将在[第十三章](B15413_13_Final_SK_ePub.xhtml#_idTextAnchor174)使用分类和聚类以及[第十四章](B15413_14_Final_SK_ePub.xhtml#_idTextAnchor184)计算思维和 Python 在统计分析中中简要探讨,以便我们学习如何操作和学习数据。

在我们转向另一个库之前,关于成对图的另一件事——如果有 10 列数据,成对图将比较每一列与自身,然后与其他可用的每一列,创建一个相当大的显示,显示所有比较的变量。我们将在第十四章计算思维和 Python 在统计分析中中再看一些成对绘图。还有第十五章应用计算思维问题,以及第十六章高级应用计算思维问题中的问题,将让我们更多地练习这些图表以及我们可以从中得到的东西。

Seaborn 库有助于可视化统计数据,类似于 Matplotlib。Seaborn 内置函数的易用性使其成为可视化和分析的强大工具。

现在,让我们来看看 SciPy 库。

使用 SciPy 库

SciPy 库主要用于解决科学和数学问题。以下是一些有用的子包及其用途:

  • cluster 用于聚类算法。

  • constants 包含物理和数学常数和单位,如golden(黄金比例)和mu_0(磁常数)。

  • fftpack 利用快速傅里叶变换例程。

  • integrate 用于微分方程求解器。

  • interpolate 包含插值和平滑样条。

  • io 与输入和输出有关。

  • linalg 与线性代数相关。

  • ndimage 用于处理N维图像。

  • odr 用于正交距离回归。

  • optimize 用于优化和寻根例程。

  • signal 用于信号处理。

  • sparse 用于稀疏矩阵和相关例程。

  • spatial 用于空间数据结构和算法。

  • special 用于特殊函数(如椭圆函数和积分)。

  • stats 用于统计分布和函数。

在每个子包中,SciPy 包含许多函数,以帮助可视化和优化科学数据。因为它是专门为此目的而创建的,所以它是科学领域常用的工具。也就是说,统计包也很强大,因此即使在非科学统计分析中,该库也很有帮助。

让我们现在来看看 Scikit-Learn 库,这将在本书的后续章节中使用。

使用 Scikit-Learn 库

Scikit-Learn 可能是 Python 中最重要的机器学习库。在接下来的章节中,我们将在样本中探索这个库,探讨一些适合机器学习的问题,因此我们不会在这里深入探讨功能。也就是说,以下是 Scikit-Learn 给我们的一些功能:

  • 聚类有助于对未标记的数据进行分组。

  • 回归衡量变量(变量的平均值)与其他变量的值之间的关系。

  • 分类在 Scikit-Learn 中有多个分类器,类似于回归。一些分类器是线性判别分析装袋分类器K 最近邻分类器等。

  • 模型选择具有用于在机器学习中创建训练和测试模型的工具。

  • 预处理包含用于标准化数据集的工具(有关数据预处理的更多细节可以在第十四章计算思维和 Python 在统计分析中中找到)。

Scikit-Learn 库是我们在接下来的例子中会比较熟悉的内容,所以我们将一些讨论留到[第十四章](B15413_14_Final_SK_ePub.xhtml#_idTextAnchor184),计算思维和 Python 在统计分析中,[第十五章](B15413_15_Final_SK_ePub.xhtml#_idTextAnchor199),应用计算思维问题,以及[第十六章](B15413_16_Final_SK_ePub.xhtml#_idTextAnchor219),高级应用计算思维问题,所以我们将一些讨论留到那些章节。

我们在 Python 中拥有的库和包使我们能够对数据集进行详细分析,并创建各种有用的图表,这有助于数据分析。

总结

在本章中,我们讨论了实验数据和有效性、可靠性以及在实验环境中的普适性的定义。我们还讨论了如何安装和使用 pandas、NumPy 和 Matplotlib 库,以便我们可以使用它们来组织和显示数据。您学到的一些技能包括定义实验、数据收集,以及计算思维如何帮助我们定义问题并设计用于显示结果的工具。

此外,我们还了解了数据分析和数据科学在当前世界中的增长和重要性。我们能够使用库来生成代表数据文件子集的 Matplotlib 条形图。

在下一章中,我们将学习更多关于数据以及数据科学和数据分析的应用。

第十三章:使用分类和聚类

在本章中,我们将使用 Python 编程语言的分类和聚类能力。我们将使用计算思维元素来定义处理聚类和分类问题时所需的组件。

在本章中,我们将涵盖以下主题:

  • 定义训练和测试

  • 实施数据聚类

通过本章的学习,您将能够设计最适合所提出场景的算法。您还将能够识别与所提出问题最符合的 Python 函数,并概括您的解决方案。

技术要求

我们将需要最新版本的 Python 和Scikit-Learn来执行本章中的代码。

您可以在这里找到本章中使用的代码:github.com/PacktPublishing/Applied-Computational-Thinking-with-Python/tree/master/Chapter13

您可以在这里找到Pima Indians Diabetes Databasewww.kaggle.com/uciml/pima-indians-diabetes-database

数据训练和测试

在本节中,我们将学习如何使用 Python 工具和库为训练和测试数据创建模型。在处理数据和数据科学时,有时我们希望训练算法继续从数据中收集和学习。然后数据训练用于数据挖掘和机器学习。首先,让我们定义训练数据集

  • 这是用来拟合模型的数据样本。

  • 这是一个实际数据集,用于训练模型(在神经网络的情况下是权重和偏差)。训练模型从这些数据中“看到”和“学习”。

在计算机领域,神经网络是使用人类和动物大脑中的生物神经网络作为灵感创造的计算系统。当使用训练数据集时,例如在创建机器学习模型时,模型严重依赖数据。但是什么是机器学习? 机器学习,或ML,是人工智能AI)的一种应用,允许机器在没有明确编程的情况下自动学习使用程序。

没有高质量的数据训练基础,算法就是无用的。ML 中的数据训练是指用于开发模型的初始数据。它们发现关系,发展理解,发现模式和趋势。输入数据被馈送到 ML 算法和所有相关的技术中,以产生输出。该输出也被反馈到模型中作为更新的反馈,进而提供用作再次输入的反馈数据。这个过程是循环的,不断地适应和学习。下图显示了一个简单的 ML 图形,展示了发生的过程:

图 13.1 - 机器学习及其与输入和输出的关系

图 13.1 - 机器学习及其与输入和输出的关系

在 ML 中,数据与统计工具结合起来预测输出。机器接收输入数据并使用算法构建答案。这类似于数据挖掘,其中使用大型数据集来发现异常、相关性、模式等,以预测结果。

在数据挖掘中,我们提取信息,但使用方法提取必要的、相关的、无误的数据点。也就是说,通过数据挖掘,我们从数据集中提取我们需要的内容,而不提取那些异常值,同时还要查看数据中的相关性和模式。数据挖掘和 ML 之间的区别在于 ML 分析输入数据和输出数据。因此,一旦输出被处理,它就会回到算法中,被馈送回输入数据,并重新处理。这个循环是持续的,正如您可以从前面的图表中看到的那样。

重要提示:

有四组 ML 算法,但在本书中,我们只会介绍其中两种。虽然这不是一本 ML 书,但 Python 在 ML 领域的应用仍在不断增长,因此了解编程语言的应用以及如何使用计算思维来解决问题与我们的目标相关。

在 ML 中,我们使用两种重要的技术:

  • 监督学习映射数据对,使用输入数据和期望输出(训练数据),以便模型可以找到潜在的模式。

  • 无监督学习使用无标签的训练数据进行结论。

此外,还有两种我们不会在本书中介绍的技术:半监督学习和强化学习。

在监督学习中,学习算法被提供一组输入以及它们的期望输出(也称为标签)。目标是发现一条规则,使计算机能够重新创建输出,或者换句话说,映射输入和输出。另一方面,无监督学习允许我们在几乎不知道我们的结果应该是什么的情况下解决问题。输出变量是无标签的。使用无监督学习,算法被提供一组输入,但没有期望的输出,这意味着算法必须自行找到结构和模式。

以下图表显示了监督学习和无监督学习的路线图:

图 13.2 - 机器学习类型

图 13.2 - 机器学习类型

从前面的图表中可以看出,我们有两种监督学习。当我们获得训练数据和期望的输出时,我们使用回归分类。使用回归,我们预测连续值输出。使用分类,我们得到离散值输出(0 或 1)。回归的一个例子是预测某一天会有多少降雨,而分类则是想知道是否会下雨。

对于无监督学习,前面的图包含了聚类的示例。在聚类中,我们获得训练数据,但只有少量期望的输出。聚类的一个例子是分组,它会从大量数据中获取项目并对其进行分组。

我们可以将这些类型的学习风格应用于人工神经网络。神经网络的训练通常是通过确定网络的处理输出(通常是预测)与目标输出之间的差异来进行的。这就是错误,因此网络根据学习规则调整其加权关联,并使用此错误值进行调整。神经网络通常组织成层。层由包含激活函数的多个相互连接的节点组成。

激活函数用于决定神经元是否被激活。为此,计算加权和,然后添加偏差。我们使用激活函数为神经元提供非线性的输出。最常见的三种激活函数如下:

  • Sigmoid可以表示为,其中实数的输入为x。它返回一个在-1 和 1 之间的值。

  • Tanhtanh(x)给出。它是一个具有实数输入x的双曲正切函数;它返回一个在-1 和 1 之间的值。

  • 修正线性单元ReLU)是一个分段线性函数。如果输入是正数,则输出与输入相同,否则为 0。

以下图表显示了与上述每个激活函数相关的图表:

图 13.3 - 激活函数

图 13.3 - 激活函数

模式通过输入层呈现给网络,该层与一个或多个隐藏层通信,实际处理是通过一组加权连接完成的。隐藏层然后链接到一个输出层,答案作为下图所示的输出输出:

图 13.4 – 人工神经网络(ANN)模型

图 13.4 – 人工神经网络(ANN)模型

正如您所见,求和运算符()接受输入值并通过网络创建输出。它们必须被求和,并在进入新节点时返回单个值。激活函数本质上是压缩输入并将其转换为表示节点应该贡献多少的输出值(即节点何时应该触发)。当节点被激活时,节点被认为是激活的。它获取输出值并将其转换,以便下一个节点可以将其作为输入。这称为激活

现在让我们看看如何使用 pandas 对数据进行分类。

分类数据示例

现在让我们看一个分类数据的例子。以下屏幕截图显示了使用监督学习的示例。为了生成屏幕截图中可以看到的输出,我们使用了来自www.kaggle.com的现有数据集。该数据集称为皮马印第安人糖尿病数据库。它描述了皮马印第安患者是否被诊断出患有糖尿病:

图 13.5 – 无监督学习示例

图 13.5 – 无监督学习示例

正如您所见,表格中的属性,也称为输入变量x),如下所示:

  • 怀孕次数

  • 口服葡萄糖耐量试验 2 小时后的血浆葡萄糖浓度

  • 舒张压(mm Hg)

  • 三头肌皮褶厚度(mm)

  • 2 小时血清胰岛素(mu U/ml)

  • 体重指数

  • 糖尿病谱系功能

  • 年龄(年)

对于输出变量y),我们有类变量(0 或 1)。从数据集中,每一行代表一个患者,以及该人在过去 5 年内是否被诊断出患有糖尿病。正如您所见,有八个输入变量和一个输出变量(如前图所示的最后一列)。

我们将使用二元分类模型(1 或 0),将输入变量(x)的行映射到输出变量(y)。这将总结为y = f(x)。以下代码片段使用此信息来获取我们的输出。请注意,我们将在整个示例中逐步讨论完整的文件:

ch13_diabetesA.py

import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
dataset = pd.read_csv('diabetes.csv')

正如您从前面的片段中所看到的,我们正在上传名为diabetes.csv的数据集(来自 Kaggle)。如果您需要提醒如何保存文件并找到所需的路径,请参阅第十二章使用 Python 进行实验和数据分析问题中,了解使用 Python 进行数据分析*部分。有许多上传数据集的方法。

就像我们在第十二章中所做的那样,我们使用非常流行的pandas并将其导入为pd。Pandas 用于数据操作和分析。它提供了用于操作数值表和时间序列的数据结构和操作。来自 pandas 的read_csv()函数处理从逗号分隔值CSV)中导入数据的值。

重要提示:

我们需要找到正确的目录。当您调用.csv文件时,请确保您在正确的目录中(即.csv文件所在的位置),以避免错误代码。在使用import os后使用os.chdir(),然后使用print('Current directory', os.getcwd())。有关更多信息,请参见第十二章在实验和数据分析问题中使用 Python

运行上述代码片段后,您可以查看变量资源管理器,以查看以下截图中显示的项目。请注意,变量资源管理器是一个工具,允许您浏览和管理与您的代码相关的对象。该工具是Spyder环境的一部分,它运行 Python 并具有附加功能和编辑工具,如变量资源管理器。变量资源管理器位于我们环境的右上方。以下截图显示了变量资源管理器中我们数据库的视图:

图 13.6 - 变量资源管理器示例

图 13.6 - 变量资源管理器示例

正如您所看到的,Size描述了数据集。它显示了患者数量786和变量总数9。现在我们对数据集有了更好的理解。

但是假设你不知道你将需要什么类型的学习。您可以在控制台中键入此函数,以获得数据和输出的完整图片:

dataset.describe(include='all')

以下截图显示了我们在算法中使用上述代码行后收到的信息:

图 13.7 - 运行描述算法后的信息显示

图 13.7 - 运行描述算法后的信息显示

从上图可以看出,我们能够获得所有数值特征,并知道没有分类数据。我们希望获得这些信息,因此可以使用以下代码行来查看变量之间的相关性:

dataset.corr()

这一行简单的代码帮助我们获得以下截图中显示的信息。请注意,以下截图可能会因您使用的环境而有所不同。在运行此代码时,使用SpyderJupyter等环境时,根据您的主题设置和选择,表格可能会有所不同,具有不同的颜色方案(或没有颜色方案):

图 13.8 - 数据集相关图

图 13.8 - 数据集相关图

我们可以看到所有变量与结果(输出(y))之间的相关性。前面的截图显示了血浆葡萄糖与结果的最强相关性,胰岛素的相关性最低。

现在我们对数据集有了更好的理解,让我们将输入变量和输出变量分开放入模型。让我们看一下我们的ch13_diabetesA.py文件中的以下代码片段,这为我们提供了示例:

#Split dataset into input(x) and output(y) variables 
x_variables = dataset.iloc[:,0:8]
y_variable = dataset.iloc[:,8]

我们使用print函数来检查我们的值:

print(x_variables)
print(y_variable)

运行上述代码片段后,输出数据将如下截图所示。请注意,结果显示了我们定义为变量x_variablesy_variable,这些变量又被定义为数据集的一部分,如前面的代码中所述:

图 13.9 - 用于算法打印输入和输出值的训练数据集输出

图 13.9 - 用于算法打印输入和输出值的训练数据集输出

现在我们需要将数据分割成训练数据集和测试数据集。分割技术的目的是评估 ML 算法的性能。它仅适用于任何类型的监督学习算法。第一组(训练数据集)用于拟合模型。

主要目标是将其拟合到具有已知输入和输出的可用数据上,然后对将来的新示例进行预测,在那里我们没有预期的输出或目标值。

使用 Scikit-Learn 库

在处理数据和机器学习时,另一个重要的库是scikit-learnsklearn)库。该库特别适用于分类、回归、聚类、模型选择、降维等。您可能还记得第十二章在实验和数据分析问题中使用 Python,在在 Python 中使用数据库部分中,您可以使用命令提示符窗口中的pip install来安装所需的库。一旦您有了库,就可以将其导入到代码中,如下面的代码片段所示,该代码片段使用sklearn来拆分数据。需要注意的是,此代码片段是较大的ch13_diabetesA.py文件的一部分:

from sklearn.model_selection import train_test_split
X_train,X_test, y_train,y_test = train_test_split(
    x_variables, y_variable, test_size = 0.20, 
    random_state = 10)

以下是已知的参数:

  • x_variabley_variable如前所定义。

  • test_size:测试大小将占数据集的 20%。

  • random_state:它设置了随机生成器的种子,因此您的训练和测试拆分始终是确定性的。如果设置为 none,则返回一个随机初始化的RandomState对象。

以下图表显示了该过程以及每个元素如何在循环中相互作用:

图 13.10 - 机器学习中的数据循环

图 13.10 - 机器学习中的数据循环

请注意,我们将在算法中使用顺序模型。此外,我们使用keras库,它与 Python 一起使用,因此我们可以运行我们的算法与深度学习模型。确保您有keras库可用于此算法。如果您已安装了 TensorFlow,则应该已经可以访问 Keras 库。

在处理机器学习问题和算法时,您可以选择使用不同的库。我们选择了 Keras 来解决这个特定的问题。Keras 是开源的,用于创建人工神经网络。TensorFlow 是一个包含许多机器学习组件和任务的平台。

Keras 建立在 TensorFlow 之上,并使其更容易与 Python 编程语言交互。Keras 是一个更高级的 API,我们将进一步讨论。由于其容量,它有时可能比平常慢。PyTorch是另一个用于人工神经网络的库。它是一个较低级别的 API,因此运行速度更快。Keras 由Google支持,而 PyTorch 由Facebook支持。两者都很有帮助,因此决定使用哪一个通常是开发人员的偏好。就我个人而言,我更喜欢 Keras 库。

顺序 API 允许您逐步创建模型层。还有另外两个可用的模型,功能 API模型子类化。我们将使用顺序 API,因为它是最简单的架构,而功能 API 用于深度学习(复杂模型)和模型子类化。

有几件事情我们应该注意使用 Keras:

  • 模型类是根类,用于定义模型的架构。

  • 像 Python 本身一样,Keras 使用面向对象的编程,这意味着我们可以添加子类。

  • 模型中的子类是可定制的。

尽管如此,还应该注意到,与使用顺序或功能 API 相比,子类化更具挑战性。现在让我们看一下使用我们的 Keras 库更新的算法。记得包括你的文件目录或将.csv文件保存到必要的目录以正确运行算法:

ch13_diabetesB.py

from sklearn.model_selection import train_test_split
from keras import Sequential from keras.layers import Dense
#Defining the Model
model = Sequential()
model.add(Dense(12, input_dim=8, activation='relu'))
model.add(Dense(15, activation='relu'))
model.add(Dense(8, activation='relu'))
model.add(Dense(1, activation='sigmoid'))

从前面的代码片段中,我们可以看到我们添加了四个密集连接的层。

第一层建立如下:

  • 12 个神经元

  • input_dim = 8(即,输入进入网络的输入值)

  • 激活'relu'

如您所见,我们已经添加了多个模型并对其进行了定义。为了编译模型,我们使用了同一代码文件中包含的以下代码片段。我们还可以设置model.fit来使用我们的库和以下代码,这是我们的ch13_diabetesB.py文件的一部分:

#Compile the model
model.compile(loss='binary_crossentropy', optimizer='adam', 
    metrics=['accuracy'])
#Fit the model on the dataset
model.fit(x_variables, y_variable, epochs=95, 
    batch_size=25)
#Evaluate the model
_, accuracy = model.evaluate(x_variables, y_variable)
print('Accuracy: %.2f' % (accuracy*100))
model.summary()

前面的代码编译了 Adam 优化器。Adam 优化器用于随机梯度下降,并使用训练数据迭代更新网络权重。一旦我们运行我们的代码,输出会提供以下信息:

图 13.11 - 使用 Keras 库运行模型的输出

图 13.11 - 使用 Keras 库运行模型的输出

请注意,当您运行算法时,准确性可能会有所不同。多次测试算法以查看窗口中的变化。在运行准确性模型后,我们打印模型摘要,使用model.summary(),如下所示:

图 13.12 - 算法的模型摘要

图 13.12 - 算法的模型摘要

现在我们已经看到如何使用糖尿病数据文件运行我们的算法,让我们简要地看一下一些优化模型,这将帮助我们评估算法。我们不会深入研究这些算法,但我们想提到一些可用于我们的各种工具。在建模时,我们使用优化模型,如二元交叉熵Adam 优化算法梯度下降

定义优化模型

让我们看看模型的类型。请注意,我们并没有深入研究这些模型的使用,但建议进一步探索它们在我们算法中的应用。

二元交叉熵模型

在二元分类中,我们使用交叉熵作为默认的损失函数。损失函数是一种帮助我们评估算法如何对数据建模的方法。有了损失函数,我们可以使用优化来产生更准确的结果,即它有助于减少预测误差。当目标值在二进制集中时,我们使用损失函数。

交叉熵是一种损失函数,用于计算两个概率分布之间的差异。在使用逻辑回归人工神经网络优化分类模型时,我们可以使用交叉熵。

Adam 优化算法

Adam 算法是一种随机优化方法。当函数中存在随机性以最大化或最小化函数的值时,使用随机优化。Adam 优化算法适用于一些非凸的简单优化问题。它高效且占用内存少,但可以应用于大型数据集。

梯度下降模型

梯度下降算法是一种一阶优化算法。一阶指的是线性局部误差。我们在可微分的函数上使用梯度下降来找到局部最小值。

混淆矩阵模型

混淆矩阵也被称为错误矩阵。混淆矩阵在视觉上很有帮助,因为它以表格形式呈现算法的性能,这样可以更好地可视化性能。它通常用于监督学习。

正如所述,这只是一些基本信息,当您开始优化算法时。关于 ML 的更多信息可以在其他 Packt 图书中找到,例如Python 机器学习Python 探索性数据分析

在我们进入对聚类的介绍之前,让我们快速回顾一下在本节中使用 Keras 包和模型学到的内容:

  • 加载数据

  • 在 Keras 中定义神经网络

  • 使用高效的数值后端编译 Keras 模型

  • 在数据上训练模型

  • 在数据上评估模型

  • 使用模型进行预测

现在让我们转向数据聚类。

实施数据聚类

在本节中,我们将看一下如何处理数据聚类。首先,让我们定义一下我们所说的数据聚类。数据聚类是指我们如何将数据分成组或簇。如果簇能够提供对领域知识的扩展理解,那么簇就是有意义的。我们在许多应用中使用聚类,比如医学领域,聚类可以帮助识别一组患者对治疗的反应,或者市场研究,聚类用于根据特定群体的特征来对消费者进行分组以吸引该群体。

在本讨论中,我们将看一下合成簇而不是应用簇。在第十六章高级应用计算思维问题中,你将看到一些上下文中的簇的例子。合成簇是由合成数据集生成的。也就是说,我们使用算法生成数据集。让我们看一下下面的代码片段:

ch13_syntheticDataset.py

from numpy import where
from sklearn.datasets import make_classification
from matplotlib import pyplot
#Create a synthetic dataset
X, y = make_classification(n_samples = 1800, 
    n_features = 2, n_informative = 2, n_redundant = 0, 
    n_clusters_per_class = 1, random_state=4)
#Scatterplot
for class_value in range(2):
	row_ix = where(y == class_value)
	pyplot.scatter(X[row_ix, 0], X[row_ix, 1])
#Display plot
pyplot.xlabel('variable 1')
pyplot.ylabel('variable 2')
pyplot.title('Synthetic data graph')
pyplot.show()

从前面的代码片段中可以看到,我们确定了样本数量、特征数量和簇的数量等等。此外,我们还创建了合成数据的散点图并绘制了结果。下面的图展示了我们的合成数据集作为散点图的结果:

图 13.13 – 合成数据集散点图

图 13.13 – 合成数据集散点图

请注意,这个合成数据集的样本数量为1800。尝试改变样本数量来看看散点图的变化。现在我们有了数据集,我们可以开始应用聚类算法。以下是一些常见的聚类算法:

  • BIRCH 算法

  • K 均值聚类算法

我们将在接下来的章节中看到上述的算法。

使用 BIRCH 算法

平衡迭代减少和层次聚类BIRCH)是一种聚类算法,只要有足够的可用内存和时间,就可以使用聚类中心。对于 BIRCH 和 K 均值聚类算法,我们将分享一个算法和相应的图表,以便更好地理解它们。

以下代码片段展示了 BIRCH 算法:

ch13_BIRCH.py

from numpy import unique
from numpy import where
from sklearn.datasets import make_classification
from sklearn.cluster import Birch
from matplotlib import pyplot
#Synthetic dataset definition
X, _ = make_classification(n_samples = 1800, 
    n_features = 2, n_informative = 2, n_redundant = 0, 
    n_clusters_per_class = 1, random_state = 4)
#Define the BIRCH model
model = Birch(threshold = 0.01, n_clusters = 2)
model.fit(X)
yhat = model.predict(X)
#Clusters
clusters = unique(yhat)
#Display
for cluster in clusters:
	row_ix = where(yhat == cluster)
	pyplot.scatter(X[row_ix, 0], X[row_ix, 1])
pyplot.show()

请注意,在这个示例中我们任意选择了两个簇,就像你在代码行model = Birch(threshold = 0.01, n_clusters = 2)中看到的一样。我们坚持使用我们的 1800 个样本,这样我们可以比较我们的输出图。下面的截图展示了两个样本的 BIRCH 模型。第一个(左侧)展示了前面代码片段中提供的算法运行的情况。第二个(右侧)展示了相同的算法运行,但是为了三个簇。

为了运行第二个图,我们将模型行代码更改为以下内容:

model = Birch(threshold = 0.01, n_clusters = 3) 

看一下下面的图,显示了n_clusters = 2n_clusters = 3的情况:

图 13.14 – 分别使用 2 和 3 个簇的 BIRCH 模型

图 13.14 – 分别使用 2 和 3 个簇的 BIRCH 模型

请注意,在前面截图的左侧,清楚地显示了两个簇。与图 13.13相比,你可以看到一些数据点已经被转换以适应每个被识别的簇。前面截图的右侧的散点图将数据分成了三个不同的簇。为了更加熟悉聚类算法,改变参数来看看当你改变簇的数量、样本大小和其他参数时会发生什么。

现在让我们来看一下 K 均值聚类算法。

使用 K 均值聚类算法

K 均值聚类算法是最常用的聚类算法之一。该算法将示例分配到每个识别的聚类中以最小化方差。与 BIRCH 算法类似,我们在算法中设置了聚类的数量。让我们看一下 K 均值代码片段:

ch13_KMeans.py

from numpy import unique
from numpy import where
from sklearn.datasets import make_classification
from sklearn.cluster import KMeans
from matplotlib import pyplot
#Dataset definition
X, _ = make_classification(n_samples = 1800, 
    n_features = 2, n_informative = 2, n_redundant = 0, 
    n_clusters_per_class = 1, random_state = 4)
#Model identification and fit
model = KMeans(n_clusters = 2)
model.fit(X)
#Clusters
yhat = model.predict(X)
clusters = unique(yhat)
#Display
for cluster in clusters:
	row_ix = where(yhat == cluster)
	pyplot.scatter(X[row_ix, 0], X[row_ix, 1])
pyplot.show()

再次注意,我们使用相同数量的聚类(2)和样本数量(1800),以便进行比较。以下屏幕截图显示了前述算法产生的 K 均值散点图输出:

图 13.15 – K 均值算法输出

图 13.15 – K 均值算法输出

请注意,数据仍然完全相同,然而,当我们比较从 BIRCH 算法和 K 均值算法得到的显示时,您会发现我们的算法为我们的聚类产生了非常不同的结果。

我们可以使用和测试许多其他的聚类算法。了解它们并比较结果对于确定基于真实数据集使用哪些算法是至关重要的。在这种情况下,K 均值算法的结果并不真正适合模型。当使用两个聚类时,BIRCH 模型似乎更合适,因为 K 均值算法中的方差是不均等的。

随着我们从聚类示例中继续前进,请注意,对于大部分数据科学和机器学习而言,我们使用模型和算法的越多,我们就越了解它们的用途,以及何时使用这些模型是合适的,我们可以学会通过视觉识别算法是否适合我们的数据。

总结

在本章中,我们学习了如何使用 Python 编程语言中可用的一些包来创建大型数据集的模型。我们使用了 Keras 等包来上传数据和定义神经网络。我们训练了模型并评估了模型。我们使用模型进行预测。我们还学习了数据的分类和测试以及如何处理数据聚类。阅读完本章后,您现在可以定义数据训练以及 Python 在数据训练中的用途。您还可以定义和使用聚类算法。

我们将在下一章继续探讨这里讨论的一些主题。我们还将在第十六章中提供的示例中看到一些这些应用,高级 应用计算思维问题

第十四章:在统计分析中使用计算思维和 Python

在本章中,我们将使用 Python 和计算思维的元素来解决需要统计分析算法的问题。我们将使用pandas DataFrames在 Python 环境中创建统计分析算法。在 Python 中还需要其他软件包来创建统计分析,例如NumPypytz等。当我们需要处理的代码和学习这些库帮助我们做什么时,我们将使用这些软件包,比如使用 pandas 整理数据。

在本章中,我们将涵盖以下主题:

  • 定义问题和 Python 数据选择

  • 数据预处理

  • 使用可视化处理、分析和总结数据

在本章结束时,您将能够设计最适合您所面临情况的算法。您还将能够确定与所提出问题最符合的 Python 函数,并概括您的解决方案。

技术要求

您需要最新版本的 Python 来运行本章的代码。

在本章的问题中,您需要安装pandasNumPySciPyScikit-Learn软件包。

您可以在本章中找到使用的完整源代码:github.com/PacktPublishing/Applied-Computational-Thinking-with-Python/tree/master/Chapter14

定义问题和 Python 数据选择

在我们看 pandas 库之前,让我们定义一下数据分析是什么。当我们谈论数据分析时,我们指的是检查、清洗、转换和建模数据的过程,目的是发现有用的数据,通知结论,并支持决策。决策是至关重要的。我们不只是想看看数据在过去发生了什么。我们希望利用数据来为未来做出知情决策。

看一下数据分析的一些用途:

  • 商业:它有助于基于客户趋势和行为预测做出决策,提高业务生产力,并推动有效的决策。

  • 天气预报:收集和分析大气数据(温度、湿度、风力等)以了解大气过程(气象学),从而确定大气将来的演变。

  • 交通:数据可用于确定趋势,包括交通、事故等,帮助我们做出关于交通模式、交通灯持续时间等的决策。

上述用途只是可能应用的一部分。数据分析被用于非常广泛的事情,由企业和教育组织做出关键决策,为社区提供资源,资助我们的学校和大学,以及更多。

因此,让我们看看我们有哪些工具可用于分析数据。在 Python 中用于数据分析的主要库之一是 pandas 软件包。pandas 的伟大之处在于其易用性、简单的数据结构和高性能。使用 pandas 简化了我们在数据分析中的工作。

定义 pandas

重要的一点是 pandas 是建立在 NumPy 之上的。NumPy是一个帮助我们处理数组的软件包。Python 本身并没有数组,因此这些软件包允许我们创建、使用数组,然后在此基础上构建。

Pandas 提供了一种灵活且简单的数据结构,可以简化您在数据分析中的工作。它是处理大数据的强大工具。当我们谈论大数据时,我们指的是结构化和非结构化数据集,这些数据集经过分析,以便我们可以获得更好的见解,并帮助业务和组织制定决策或策略。pandas 可以处理导入不同格式的数据,如.csv文件、SQLJSON,以及各种操作,如选择数据、合并、重塑和清理数据。

在 pandas 中有两种不同的存储数据的方式——系列和数据框:

  • Series是一维数组,可以容纳任何数据类型(整数、字符串或浮点数);系列代表一列数据。

  • 数据框是可以具有多个列和数据类型的二维对象。它接受诸如字典、系列、列表和其他数据框的输入。

现在让我们学习何时使用 pandas。

确定何时使用 pandas

总的来说,Pandas 在一般情况下非常有用,但当我们处理大量数据并使用逗号分隔值CSV)文件时,它真的是一个很好的工具。这些文件被存储为表格,如电子表格。另一件事是我们可以在 pandas 中建立。是的,在 pandas 中有一个chunksize参数,可以帮助我们分解数据。假设我们有 500 万行。我们可以决定使用chunksize将其分解为 100 万行。

此外,有时我们有大量的数据文件,但只想查看其中的一些组件。Pandas 允许我们标识要包含的列和要忽略的列。

处理 pandas 系列

如前一节所述——定义 pandas——系列是一维的。我们可以使用一些简单的代码创建一个空的 pandas 系列。请注意,通常在使用包和库时,我们首先导入 pandas 库,如下所示:

import pandas as pd
demo_series = pd.Series()
print(demo_series)

默认创建的系列将具有float类型,因为我们在算法中没有建立其他类型。然而,控制台打印了一个警告,即在将来,空系列的dtypes将被设置为对象,而不是floatdtype代表数据类型;在这种情况下,它是一个浮点数。看一下以下截图,显示了我们运行算法时的输出:

图 14.1 – 在 pandas 中创建空系列时未识别 dtype 时的输出

图 14.1 – 在 pandas 中创建空系列时未识别 dtype 时的输出

正如您所看到的,这里实际上并没有错误,只是关于数据存储方式的警告。

现在,让我们创建一个具有定义元素的系列。为此,我们需要导入 pandas 和numpy,以便我们可以创建数组,然后创建系列。看一下以下代码片段:

ch14_seriesDemo.py
import pandas as pd
import numpy as np
dataset = np.array(['yellow', 'green', 'blue', 'violet', 'red'])
data_series = pd.Series(dataset)
print(data_series)

正如您从前面的代码中看到的,我们使用numpy存储了我们的数组,然后使用该数组创建了一个系列。最后,我们打印了系列。输出是一个表格,如下所示:

0    yellow
1     green
2      blue
3    violet
4       red
dtype: object

我们也可以通过首先创建一个列表来获得完全相同的结果。看一下这段代码:

ch14_seriesDemo2.py

import pandas as pd
import numpy as np
myList = ['yellow', 'green', 'blue', 'violet', 'red']
data_series = pd.Series(myList)
print(data_series)

请注意,我们直接从列表中创建了系列。我们不打算展示这段代码的输出,因为它与上一段代码的输出完全相同。

我们也可以从字典中获得一个系列。让我们看一下以下代码片段:

ch14_seriesDemo3.py

import pandas as pd
myDictionary = {
    'Name' : 'Miguel',
    'Number' : 42,
    'Age' : 'unknown'
    }
mySeries = pd.Series(myDictionary)
print(mySeries)

前面的代码只是一个演示,但当我们运行算法时,我们确实得到了一个包含字典值的表系列。让我们看一下输出:

Name       Miguel
Number         42
Age       unknown
dtype: object

正如您所看到的,我们有基于键值对的两列,以及类型为object。一个重要的一点是系列没有列标题。为此,我们需要使用数据框。

如果我们想要访问系列中的特定元素怎么办? 好吧,要访问第一个元素,让我们使用以下代码片段:

ch14_demo4.py

import pandas as pd
myDictionary = {
    'Name' : 'Miguel',
    'Number' : 42,
    'Age' : 'unknown'
    }
mySeries = pd.Series(myDictionary)
print(mySeries[0])

前面代码的输出只是Miguel。这是因为我们使用索引 0 来标识我们从字典中想要的内容,因此它将为我们提供第一个键值对的值。如果我们想要前两个键值对元素的值对,我们将(mySeries[0])替换为(mySeries[:2])。然后,输出将如下所示:

Name      Miguel
Number        42
dtype: object

系列还有许多其他事情可以做,因此可以尝试使用索引和使用列表、字典或 NumPy 数组创建不同类型的系列。现在,让我们继续学习数据框。

使用 pandas 数据框

现在,让我们来看看如何处理数据框。首先,让我们来看一个带有 pandas 的.csv文件。我们将在接下来的代码片段中使用demo.csv文件。请将文件的位置替换为您保存文件的位置,该位置可以在 GitHub 存储库中找到:

ch14_csvDemo.py

import pandas as pd
data_set = pd.read_csv('C:\\...\\demo.csv')
data_set.head()

前面的代码做了三件事。它导入了 pandas 包,以便我们可以使用我们需要的数据功能,它告诉程序打开我们将要使用的数据文件,然后它给出了文件中的前几行数据,以便我们可以看到我们正在处理的内容。以下截图显示了前面代码的结果或输出:

图 14.2 - 显示数据集的前几行输出

图 14.2 - 显示数据集的前几行输出

从前面的截图中可以看出,表格未显示文件中包含的所有值。虽然我们的文件不是充满大数据,但它确实包含更多的信息行。这只是让我们预览我们的数据的一种方式。

但这是一个干净的数据集。如果我们有一个数据集的行或列缺少信息会发生什么? 好吧,pandas 允许我们处理文件以准备好使用。数据框还可以准备我们的数据,以便我们可以创建可视化表示。这些可视化或图表将允许我们看到趋势,进行预测,确定我们可以用于训练的值,等等。数据框实际上只是我们可以用数据集做的其他一切的基础。

在这一部分,我们学习了问题,如何使用 pandas 以及 pandas 系列和数据框的一些功能。

作为一种注意,在本章的其余部分,我们将更加专注于数据框而不是系列。但在进入应用程序之前,让我们看看如何通过预处理数据框来避免错误和陷阱。

数据预处理

预处理数据是一种将原始数据转换为可用和高效格式的技术。实际上,这是数据挖掘和机器学习过程中最重要的步骤。

当我们预处理数据时,我们实际上是在清理它、转换它或进行数据减少。在本节中,我们将看看这些都意味着什么。

数据清理

数据清理指的是使我们的数据集更高效的过程。如果我们在真正大型的数据集中进行数据清理,我们可以加快算法,避免错误,并获得更好的结果。在数据清理时,我们处理两件事情:

  • 缺失数据:这可以通过忽略数据或手动输入缺失数据的值来解决。

  • 嘈杂的数据:这可以通过使用分箱、回归或聚类等其他过程来修复/改进。

我们将更详细地看看这些事情。

处理缺失数据

让我们看看我们如何处理缺失数据。首先,我们将学习如何忽略缺失数据。我们可以使用 pandas 找到具有缺失值的行。当我们这样做时,我们正在清理我们的数据集。现在,我们不会介绍我们可以使用的每种方法,只介绍一种可以去除具有缺失值的行的方法。与往常一样,使用的数据集可在我们的 GitHub 存储库中找到,并且您需要更新文件位置。让我们看看以下代码片段:

ch14_cleaningDemo1.py

import pandas as pd
myData = pd.read_csv('C:\\...\\demo_missing.csv')
print(myData)
cleanData = myData.dropna(axis = 0, how = 'any')
print(cleanData)  

在前面的代码中,第一个print语句是为了我们自己看到我们的数据集是什么样子的。您绝对不会想在大文件中这样做!以下截图显示了第一个print输出:

图 14.3 - 第一个打印语句,原始数据集

图 14.3 - 第一个打印语句,原始数据集

请注意,1Blue列在Countries下有一个NaN值,下一列(2Yellow)在Numbers列下有一个缺失值。当我们使用dropna()时,算法将删除具有缺失值的行。以下截图显示了带有修改后数据集的打印语句:

图 14.4 - 打印干净的数据集

图 14.4 - 打印干净的数据集

从前面的截图中可以看到,缺少值的两行在我们的新数据集中被消除了。现在,我们可以对这些数据运行任何我们想要的分析。

如果您只想检查一个列以验证是否存在缺失值,您可以使用以下代码片段:

ch14_cleaningDemo2.py

import pandas as pd
myData = pd.read_csv('C:\\...\\demo_missing.csv')
print(pd.isna(myData['Countries']))

请注意,在前面的算法中,我们使用Countries列标题来验证特定列。当我们运行算法时,我们的输出如下:

0    False
1     True
2    False
3    False
4    False
5    False
6    False
7    False
8    False
9    False
Name: Countries, dtype: bool

如您所见,我们的数据集中的第二行在Countries列中有一个缺失值。

虽然我们不会详细介绍每种方法,但您也可以删除列。您可以选择删除具有一定数量缺失值的行和/或列。例如,您可以选择仅删除具有两个以上缺失值的行或列。如果这样做,您仍然需要担心可能仍然缺少值的列或行,因为只有一个缺失值而未被删除。对于这些情况,您可以选择用其他值替换缺失值。

假设您想要根据列替换一个值。要这样做,让我们看一下以下代码片段:

ch14_cleaningDemo3.py

import pandas as pd
myData = pd.read_csv('C:\\...\\demo_missing.csv')
print(myData.fillna(0))

从前面的代码中可以注意到,我们正在用值0填充每个空单元格。当我们运行算法时,我们得到以下输出:

图 14.5 - 替换缺失值

图 14.5 - 替换缺失值

请注意前面截图中突出显示的值。这些是我们的算法替换的值。现在,让我们看一下我们如何处理嘈杂数据。

处理嘈杂数据

首先,让我们定义一下嘈杂数据的含义。当我们有大量数据,其中一些对我们的分析没有用处时,我们称之为嘈杂数据。嘈杂数据也用于指代数据损坏。实际上,它只是无用的数据

我们处理嘈杂数据的三种方法是分箱、回归和聚类:

  • 分箱使用相邻数据来平滑排序后的数据值。排序后的值放入箱中,这些箱是算法内创建的组。

  • 聚类方法识别并移除数据集中的异常值。

  • 回归方法通过将数据拟合到回归函数中来平滑数据。

分箱的目的是减少一些错误。在分箱中,数据被分成小的桶或箱。然后用计算出的箱值替换数据。当我们进行分箱过程时,我们正在平滑数据。

以下是算法中一个简单的数值数据集的示例。以下代码片段将创建具有相等频率的箱:

ch14_binning1.py

#Binning with equal frequency
def equal_frequency(array1, m): 
	l = len(array1) 
	n = int(l / m) 
	for i in range(0, m): 
		array = [] 
		for j in range(i * n, (i + 1) * n): 
			if j >= l: 
				break
			array = array + [array1[j]] 
		print(array) 
#Input dataset 
dataset = [3, 6, 7, 9, 11, 14, 10, 15, 19, 35, 38, 45, 48, 49, 76] 
#Input number of bins
m = 5
print("Equal Frequency Binning: ") 
equal_frequency(dataset, m) 

在查看前面的代码时,您可以看到箱的数量被定义为5,因此数据将被分成五个列表。分箱实际上是一种分组信息的方法。我们告诉算法我们想要这样做以及我们想要多少个箱,它就会提供这些箱或组中的数据。在这种情况下,我们得到了这五个列表。看一下输出:

Equal Frequency Binning: 
[3, 6, 7]
[9, 11, 14]
[10, 15, 19]
[35, 38, 45]
[48, 49, 76]

如您所见,算法创建了五个箱,每个箱中有三个值。

重要提示:

请注意,分箱过程并不为我们组织数据。因此,如果我们重新排序我们的值,它们仍然会按照数据输入的顺序进行分箱。

现在,让我们看一个使用等宽度的分箱算法:

ch14_binning2.py

#Binning with equal width 
def equal_width(array1, m): 
	w = int((max(array1) - min(array1)) / m) 
	min1 = min(array1) 
	array = [] 
	for i in range(0, m + 1): 
		array = array + [min1 + w * i] 
	arrayi=[] 
	for i in range(0, m): 
		result = [] 
		for j in array1: 
			if j >= array[i] and j <= array[i+1]: 
				result += [j] 
		arrayi += [result] 
	print(arrayi) 
#Input dataset 
dataset = [3, 6, 7, 9, 11, 14, 10, 15, 19, 35, 38, 45, 48, 49, 76, 81, 208, 221] 
#Input number of bins
m = 3
print("\nEqual Width Binning:") 
equal_width(dataset, m)

上面代码片段将我们的数据分成了三个箱。等宽度分箱的目标是将数据集分成相等大小的箱,这在等宽度分箱的情况下意味着相等的范围。数据将被分割,但重要的是要注意,我们在这里谈论的是范围,因此对于这个特定数据集,这些箱中的元素数量不会相同。上面代码片段的输出如下:

Equal Width Binning:
[[3, 6, 7, 9, 11, 14, 10, 15, 19, 35, 38, 45, 48, 49], [76, 81], [208]]

正如你所看到的,分箱产生的输出看起来并不像等频输出那样干净,但实际上更受欢迎。

现在,让我们谈谈数据转换。

转换数据

Pandas 允许我们转换数据。以下是一些我们可以转换数据的方法:

  • 归一化将值转换为新的范围;最流行的是最小-最大归一化,如下所示:

  • 属性选择是通过用不同的属性或属性替换属性来转换数据的过程。

  • 概念层次结构实际上是通过减少数据来进行的转换。它是通过用更高级的概念(如极长)替换数字(101540)等概念来完成的。

在下一节中,我们将浏览数据的减少。

减少数据

数据减少是指一种过程,允许我们从数据集中减少数据的表示,但只有在减少数据的体积后才能获得类似甚至相同的结果。

我们不会在这里深入讨论所有概念,因为在示例中查看它们会更容易,但以下是一些数据减少的方法:

  • 从数据集中删除无效数据

  • 为不同级别的数据创建汇总

将无效数据移除可以看作是处理任何异常值。数据可能已被错误输入,条件可能不是最佳的,或者类似情况。当我们有一个数据点与整个数据集不符合时,特别是当我们有大量数据点进行比较时,我们可以将该数据点作为无效数据或异常值移除。

在创建不同级别的汇总时,我们对数据集进行聚合,并在每个级别进行测试和生成汇总。假设你有 100 个数据点(数据集通常会有数千个,但用较小的数字更容易解释)。我们可以为前 20 个数据点创建一个汇总。然后,我们可以对前 40 个数据点做同样的操作,然后是前 60 个,依此类推。通过比较,我们可以看到趋势,并利用数据集的这些较小子部分来进行预测,如果趋势成立的话。这就是数据减少的作用,简化数据集的同时仍然获得准确的结果。

在这一部分,我们学习了如何处理需要清理的数据。我们学习了一些清理数据的方法,比如消除缺失数据或替换缺失点。我们还了解了嘈杂数据以及如何解决数据集中的嘈杂数据问题。最后,我们学习了数据减少以及如何通过删除无效数据和创建数据的汇总来获得准确的结果。

这只是我们处理数据时所做的一些类型的简介。所以,让我们看一个例子,以便我们能把其中一些放入上下文中。

使用可视化处理、分析和汇总数据

我们现在在房地产领域工作,因为我们想做得好,我们真的想建立一个帮助我们分析数据并预测房价的算法。但是让我们再想一想。我们可以广义地或狭义地定义这个问题。我们可以对一个州的所有房屋或一个社区中三个或更多卧室的房屋进行定价分析。进行分析重要吗?也许但这难道不是我们想要研究这个问题的原因吗?

让我们先来看看我们如何处理数据。

处理数据

让我们先收集一些数据。对于这个问题,我们使用的是kv_house_data.csv数据集,它可以在我们的 GitHub 存储库中找到。要查看这个数据集,我们需要很多库。我们主要谈论的是 Pandas,是的,但我们还想进行可视化和一些分析,所以我们还需要SeabornSciPyScikit-Learn。完整的算法可以在ch14_housePrice_prediction.py文件中找到。我们将逐步查看它,讨论我们在进行过程中所做的事情:

ch14_housePrice_prediction.py

import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt

那么这些库都是什么?我们知道 Pandas。我们使用它来组织我们的数据。NumPy 帮助我们处理数组。Seaborn 和 Matplotlib 都用于可视化。如果我们要进一步创建模型并使用数据集进行训练,我们还需要 Scikit-Learn。对于这个例子,我们将坚持在训练之前获得一些图表。

现在,让我们导入我们的数据集。还记得第十二章中提到的吗,在实验和数据分析问题中使用 Python,您可以直接设置文件的目录。您也可以提供数据文件的完整路径,就像我们在本章中之前做过的那样,比如在清理演示中。您可以使用os.chdir()来建立目录,将文件的位置添加到括号中,然后使用以下代码片段来读取.csv文件:

housing_data= pd.read_csv("kc_house_data.csv")

我们在这里使用了一个 pandas 函数。看到那个 pd.read_csv()了吗?那个pd就是 pandas,因为我们将 pandas 导入为pd,而read_csv()是允许算法获取文件中信息的函数。

如果您忘记输入您的目录或者包含错误的位置,您将会收到一个错误代码,如下面的截图所示:

图 14.6 - 文件路径错误

图 14.6 - 文件路径错误

正如您所看到的,Python 会确保您知道自己犯了一个错误。有时这可能会让人恼火,但肯定是有帮助的。

现在我们有了我们的数据,我们需要检查并清理它。这就是我们之前在本章中分享的所有内容发挥作用的地方。让我们看看实际情况是什么样子。

分析和总结数据

现在,有一件事我们并没有谈论太多,那就是 Python 的变量资源管理器。更具体地说,是Spyder 的 Python 变量资源管理器。Spyder 是一个集成环境,是免费的。它可以与 Python 一起工作,像平常一样运行 Python,但也为我们提供了更好的编辑工具。下面的截图显示了当您从 Python 的变量资源管理器中导入数据集时应该看起来的样子。当我们在 Spyder 中运行ch14_housePrice_prediction.py Python 算法时,我们可以在变量资源管理器中看到我们的变量。下面的截图显示了当我们运行这个算法时从变量资源管理器中获取的数据:

图 14.7 - Spyder 中的变量资源管理器视图

图 14.7 - Spyder 中的变量资源管理器视图

当我们处理大量数据和更大的算法时,这个工具变得非常关键。我们可以从这个工具中获得很多信息。例如,让我们看看housing_data变量。在图 14.7中,您可以看到我们算法中这个变量的类型是DataFrame,大小为(21613, 21)

如果你在变量资源管理器中双击变量,你会得到以下截图中显示的内容(请注意,截图可能会因你使用的环境而有所不同。在使用 Spyder 或 Jupyter 等环境运行此代码时,取决于你的主题设置和选择,表格可能会有所不同,有不同的颜色方案或没有颜色方案):

图 14.8 - Spyder 中的 DataFrame 变量视图

图 14.8 - Spyder 中的 DataFrame 变量视图

这只是获取 DataFrame 的一些信息的一种方式。Spyder 还允许我们调整窗口大小,这样我们可以查看更多列,滚动查找值等。如果我们在 Python 控制台中,这并不容易。你可以获取信息,只是不那么容易。

以下是可以为我们提供一些信息的代码:

housing_data.head()

上面的代码将显示我们数据集的前五行。看一下以下截图;在价格经度之间有省略号()。这是因为 Python 想让我们知道这两者之间还有其他列:

图 14.9 - DataFrame 的前几行

图 14.9 - DataFrame 的前几行

正如你所看到的,行帮助我们看到我们的数据集是什么样子,但没有其他信息。因此,我们还可以使用以下代码查看我们的 DataFrame 的大小:

housing_data.shape

当我们运行上述代码时,我们会得到以下输出:

(21613, 21)

如你所见,现在我们有了 DataFrame 的形状或大小。这是什么意思?这意味着我们有21,613行数据,21列。无论你是在 Spyder、Python 控制台还是其他你选择的环境中,你都可以看到你的数据已成功导入。

现在我们已经导入了原始数据,让我们看看是否可以获得更多信息。我们可以使用以下代码来获取摘要:

housing_data.describe()

describe()函数生成一个包括均值、标准差和百分位数等细节的摘要。这个百分位数是数据集五数概括的一部分,用于创建箱线图。虽然我们不会在这个问题中创建箱线图,但这种可视化表示对我们算法的目标可能有所帮助。看一下使用describe()函数的结果的以下截图:

图 14.10 - 使用 describe()函数

图 14.10 - 使用 describe()函数

现在,我们应该注意,该函数分析 DataFrame 中的数值。它排除了NaN值。NaN代表 Python 中的不是数字值,并表示任何未定义或不可表示的值。NaN也可以表示缺失的数字。我们之前在处理缺失数据部分讨论过这些缺失的数字以及我们可以解决它们的一些方法。现在让我们在这个背景下看一下。我们想找到我们的缺失值。为此,我们可以运行以下代码片段:

housing_data.isnull().sum()

上面的片段将让我们知道我们的数据集中每个列中是否有缺失数据点。然后,它将聚合值作为总和。因此,如果在“日期”列下有两个缺失值,我们期望在那里看到2。我们的结果可以在以下截图中看到:

图 14.11 - 运行 isnull()和.sum()函数的结果

图 14.11 - 运行 isnull()和.sum()函数的结果

我们不必清理这个数据集! 这是一个干净的数据集。然而,学习如何识别我们是否需要这样做非常重要。现在我们知道我们有一个干净的数据集,我们可以开始构建一些可视化。

使用数据可视化

请记住,这仍然是我们初始算法文件ch15_housePrice_prediction.py的一部分。如果你打开该文件,接下来的代码从第 25 行开始。我们在代码行之间添加了描述性的注释:

names=['price','bedrooms','bathrooms','sqft_living','sqft_lot','floors','waterfront','view','condition','grade','sqft_above','sqft_basement','zipcode','lat','long']
df=housing_data[names]

我们要做的第一件事是确定我们将用于图表的列。在这样做之后,我们将制作一个 DataFrame 并将其保存为df

correlations= df.corr()

前面的代码片段创建了我们 DataFrame 的相关性。在文件的下一个代码片段中,我们将创建我们的图表;也就是说,我们将进行数据可视化:

fig=plt.figure()
ax=fig.add_subplot(111)
cax=ax.matshow(correlations,vmin=-1,vmax=1)
fig.colorbar(cax)

在前面的代码中,我们命名了我们的图表,添加了子图,并确定了我们的颜色。接下来,我们需要设置一些属性,比如刻度标记、刻度标记之间的距离以及轴和刻度标记的标签:

ticks=np.arange(0,15,1)
ax.set_xticks(ticks)
ax.set_yticks(ticks)
ax.set_xticklabels(names, rotation =' 90')
ax.set_yticklabels(names)

在设置了一些属性之后,我们可以要求图表使用tight_layout()。这将帮助我们看到图表和标签的所有细节。如果我们不使用tight_layout(),有时在我们的图表中会有一些标签是不可见的:

plt.tight_layout()
plt.savefig('Correlation_graph.png',dpi = 300)
plt.show()

在前面的代码片段中,我们还创建了一个保存文件并定义了图表的大小。最后,我们要求算法显示我们的相关性。以下截图显示了前面代码的结果:

图 14.12 - 住房数据的相关性图

图 14.12 - 住房数据的相关性图

正如你所看到的,我们刚刚使用 Python 创建了一个相关性矩阵。而且这是一个相当不错的矩阵。但是什么是相关性矩阵呢? 相关性矩阵查看我们 DataFrame 中的所有值,然后计算它们之间的相关程度,给出一个在-11之间的值。数值越接近1,这些值之间的相关性就越高。每个值与自身进行比较,这是完美的相关性,当然,在我们的矩阵中被视为对角线。

其余的图表,所有的值都相互比较,必须更仔细地观察。越接近黄色,值之间的相关性就越高。因此,看一下y轴的sqft_above值和x轴上sqft_living的对应值。该值接近于黄色值1,但不完全相等。我们在以下截图中突出显示了这些值:

图 14.13 - 突出显示的 sqft_above 和 sqft_living 的相关性

图 14.13 - 突出显示的 sqft_above 和 sqft_living 的相关性

还有其他一些值显示出一定的相关性,但不够强。这个图表帮助我们做出决策,也许要更仔细地观察这种相关性,找到更多关于它的信息等。

现在,我们还可以看一下一个相当密集的图表矩阵,称为对图对图向我们展示了单个变量的分布以及两个变量之间的关系。如果我们对我们算法中包含的数据运行对图,我们会得到一个庞大的图表,如下截图所示(请注意,由于分析和绘制的数据量较大,这个图表可能需要几分钟的时间来生成):

图 14.14 - 完整的 DataFrame 对图

图 14.14 - 完整的 DataFrame 对图

正如你所看到的,这是一个相当密集的、难以阅读的、几乎不可能分析的图表。你需要放大到每对图形来做出一些决定。还应该提到的是,算法需要时间来运行并生成这个图形。从如此庞大的数据集中创建如此复杂的东西需要一定的处理能力!

我们是否想要看到像这样的大量图表?实际上是的。如果我们有较少的变量数据集,那绝对可以!我们还可以使用其他颜色方案来使其更加友好,例如。这可能会使识别趋势变得更容易。但让我们明确一点;这并不是非常有帮助。我们可以创建一些感兴趣的变量的成对图。记得我们谈到过sqft_livingsqft_above可能有很强的正相关性吗?我们也确实想要将事物与定价进行比较,对吧?所以,让我们只使用sqft_livingpricingsqft_above创建一个成对图。看一下我们文件中相关代码片段:

coln = ['price','sqft_living','sqft_lot']
sns.pairplot(housing_data[coln], height = 3);
plt.savefig('pairplotting.png',dpi =300)
plt.show()

现在,当我们运行算法的这一部分时,我们得到了下面截图中显示的图表。这个图表提供了这三个值的相关性,我们确实可以看到一些正相关发生:

图 14.15 - 价格、sqft_living 和 sqft_above 的成对图

图 14.15 - 价格、sqft_living 和 sqft_above 的成对图

特别注意一下成对图中的sqft_livingsqft_above。这两者之间的关系相当线性和正向。这证实了我们从图 14.13中观察到的情况,其中相关性比其他变量更接近1

但是,分析另外三个变量也会有所帮助,这样我们就可以看到当相关性不强时会发生什么。我们将保留pricesqft_living,这样我们只改变一个变量进行比较。看一下图 14.12sqft_livingzipcode似乎根本没有强烈的正相关性。因此,让我们再次运行算法,将zipcode替换为sqft_above。让我们看一下下面截图中显示的结果:

图 14.16 - 价格、sqft_living 和 zipcode 的成对图

图 14.16 - 价格、sqft_living 和 zipcode 的成对图

正如你所看到的,sqft_livingzipcode根本没有相关性。它们看起来更像条形图,看不到对角线。在我们离开这些图之前,值得一提的是,这些成对图仅为比较的变量提供散点图,并为图中的每个变量提供直方图。如果我们想深入了解,我们可以查看其他可视化工具,比如Seaborn中的工具。

我们在这里暂停分析。我们使用可视化来了解我们的数据有哪些相关性。如果我们进一步处理这个问题,我们可以使用数据来创建训练模型,并帮助我们进行预测。即使只是从图形数据中,我们也可以看到我们的相关性,并利用它进行预测。例如,如果我们有sqft_living,我们可以预测sqft_above,因为它们之间有很强的相关性。

Python 允许我们以多种方式查看数据。这是这样一个工具的伟大优势之一。

总结

在本章中,我们学习了如何使用 Python 处理数据,并使用本章学到的一些概念来处理房屋数据集。我们了解了 pandas 包以及它如何帮助我们组织和准备数据。我们还了解了预处理数据集的必要性,特别是在非常大的数据集中。我们处理了缺失和嘈杂的数据,以及数据转换和数据减少。我们还学会了如何使用可视化,为我们的数据集创建图表,可以帮助我们识别相关性和趋势。

本章的主题非常广泛,有整本书专门讨论这些主题。但在我们继续本书的下两章之前,我们觉得有必要分享一些 Python 编程语言的能力。

在接下来的章节中,我们将完全专注于应用程序,使用问题场景和主题来分享 Python 和计算思维在设计算法中的一些令人兴奋的应用。

第十五章:应用计算思维问题

在本章中,我们将提供多个领域的 Python 编程语言和计算思维应用示例。我们将探索多个领域,如人文学科、语言学、密码学等。我们将利用我们迄今为止学到的关于计算思维Python编程语言的知识来做以下事情:

  • 分析历史演讲

  • 编写故事

  • 计算文本可读性

  • 找到最有效的路线

  • 实现密码学

  • 实现网络安全

  • 创建聊天机器人

这一章与其他章节不同,因为我们将专注于提出问题并在评估每种情况后提供算法解决方案。

技术要求

您需要安装最新版本的 Python 才能运行本章中的代码。

您需要为 Python 安装以下库和软件包:

  • NLTK

  • Cairos

  • Pandas

  • Matplotlib

  • Seaborn

您可以在此处找到本章中使用的完整源代码:github.com/PacktPublishing/Applied-Computational-Thinking-with-Python/tree/master/Chapter15

问题 1 - 使用 Python 分析历史演讲

历史非常迷人,我们有很多原因要编写算法来评估历史数据和背景。

对于这个问题,我们想要分析一些历史文本。特别是,我们将研究亚伯拉罕·林肯的第二次就职演讲。我们的目标是找到一些单词的频率。有很多原因我们想要进行一些简单的文本分析,特别是对于历史文本。我们可能想要进行比较,了解潜在的主题等等。

对于我们的算法,我们将使用nltk包的一个相当简单的设计。由于一些组件的安装与我们迄今为止所做的有些不同,我们将提供一些信息,以防您的软件包尚未安装。

在 Python shell 中,如果您在活动控制台中,创建一个新文件并在安装主要软件包后导入nltk(使用pip install nltk)。

请注意,您不应该在活动的Shell窗口中。如果您在行首看到>>>,请单击文件 | 新建文件选项,然后输入以下代码指令行以创建空的 shell:

import nltk 
nltk.download()

从前面的代码中可以看到,您还将在nltk中打开下载器。前面的代码将弹出一个窗口,如下面的屏幕截图所示(请注意,nltk库大约需要 7MB 的内存,而安装额外的软件包也需要内存,每个软件包的范围从几 KB 到 14 到 15MB 不等):

图 15.1 - NLTK 下载器

图 15.1 - NLTK 下载器

如您所见,我的软件包都已安装。如果您的软件包尚未安装,请选择全部,然后单击该窗口左下角的下载按钮。软件包安装完成后,您可以关闭该窗口。

因为我们的问题相当简单,所以我们将跳过这个特定部分的大部分计算思维过程。我们只是试图获得演讲中使用的单词的频率。所以让我们直接进入算法,看看我们将如何使用nltk包来获得我们需要的内容,包括数据的可视化表示:

  1. 首先,我们需要导入nltkword_tokenize函数。word_tokenize函数允许我们将演讲分成单个单词和/或标点符号。我们需要演讲文本。在这种情况下,演讲被复制到算法中。您也可以将文件导入算法并以这种方式进行操作。

sent_tokenize 函数是句子标记化的缩写。与单词标记化类似,句子标记化函数允许将文本分成完整的句子。输出将包含一个由逗号分隔的句子列表。

重要提示:

重要的是要知道,所有引号都已经使用 \'\" 进行了转义,以保持原始文本而不在我们的代码中创建错误。

以下算法包含了我们需要的一切,以便分析亚伯拉罕·林肯的第二次就职演说:

ch15_historicalTextAnalysis.py

import nltk
from nltk.tokenize import sent_tokenize, word_tokenize

演讲的整个文本包含在 GitHub 存储库文件中。出于长度目的,我们在这里只包含了其中一半的文本。请注意,我们将在算法和解释之后分享的输出将对应于截断的演讲,但可视化图将包含整篇演讲的数据。在 text 定义的末尾看到的 […] 仅用于显示那里有额外的文本。

text = 'Fellow-Countrymen: At this second appearing to take the oath of the Presidential office there is less occasion for an extended address than there was at the first. Then a statement somewhat in detail of a course to be pursued seemed fitting and proper. Now, at the expiration of four years, during which public declarations have been constantly called forth on every point and phase of the great contest which still absorbs the attention and engrosses the energies of the nation, little that is new could be presented. The progress of our arms, upon which all else chiefly depends, is as well known to the public as to myself, and it is, I trust, reasonably satisfactory and encouraging to all. With high hope for the future, no prediction in regard to it is ventured. On the occasion corresponding to this four years ago all thoughts were anxiously directed to an impending civil war. All dreaded it, all sought to avert it. While the inaugural address was being delivered from this place, devoted altogether to saving the Union without war, urgent agents were in the city seeking to destroy it without war—seeking to dissolve the Union and divide effects by negotiation. Both parties deprecated war, but one of them would make war rather than let the nation survive, and the other would accept war rather than let it perish, and the war came. One-eighth of the whole population were colored slaves, not distributed generally over the Union, but localized in the southern part of it. These slaves constituted a peculiar and powerful interest. All knew that this interest was somehow the cause of the war. To strengthen, perpetuate, and extend this interest was the object for which the insurgents would rend the Union even by war, while the Government claimed no right to do more than to restrict the territorial enlargement of it. Neither party expected for the war the magnitude or the duration which it has already attained. […]'
  1. 现在我们已经定义了我们想要分析的文本,如前面的代码片段所示,我们可以告诉算法我们想要对文本进行标记化,也就是说,我们希望将其分成单词。算法将打印出一个包含每个单词或标点符号的列表,用逗号分隔,如下面的代码所示:
tokenized_word = word_tokenize(text)
print(tokenized_word)
  1. 在我们有了单词列表之后,我们想要得到单词的频率分布。为此,我们将从 nltk.probability 导入包,如下面的代码片段所示:
from nltk.probability import FreqDist
fdist = FreqDist(tokenized_word)
print(fdist)
fdist.most_common(2)
  1. 一旦我们有了分布,我们就希望得到这些数据的可视化图表,因此我们将使用 matplotlib 来创建我们的分布图,如下面的代码片段所示:
import matplotlib.pyplot as plt
fdist.plot(30, cumulative = False)
plt.show()

这就是我们需要的整个算法。当我们运行算法时,我们的输出如下:

['Fellow-Countrymen', ':', 'At', 'this', 'second', 'appearing', 'to', 'take', 'the', 'oath', 'of', 'the', 'Presidential', 'office', 'there', 'is', 'less', 'occasion', 'for', 'an', 'extended', 'address', 'than', 'there', 'was', 'at', 'the', 'first', '.', 'Then', 'a', 'statement', 'somewhat', 'in', 'detail', 'of', 'a', 'course', 'to', 'be', 'pursued', 'seemed', 'fitting', 'and', 'proper', '.', 'Now', ',', 'at', 'the', 'expiration', 'of', 'four', 'years', ',', 'during', 'which', 'public', 'declarations', 'have', 'been', 'constantly', 'called', 'forth', 'on', 'every', 'point', 'and', 'phase', 'of', 'the', 'great', 'contest', 'which', 'still', 'absorbs', 'the', 'attention', 'and', 'engrosses', 'the', 'energies', 'of', 'the', 'nation', ',', 'little', 'that', 'is', 'new', 'could', 'be', 'presented', '.', 'The', 'progress', 'of', 'our', 'arms', ',', 'upon', 'which', 'all', 'else', 'chiefly', 'depends', ',', 'is', 'as', 'well', 'known', 'to', 'the', 'public', 'as', 'to', 'myself', ',', 'and', 'it', 'is', ',', 'I', 'trust', ',', 'reasonably', 'satisfactory', 'and', 'encouraging', 'to', 'all', '.', 'With', 'high', 'hope', 'for', 'the', 'future', ',', 'no', 'prediction', 'in', 'regard', 'to', 'it', 'is', 'ventured', '.', 'On', 'the', 'occasion', 'corresponding', 'to', 'this', 'four', 'years', 'ago', 'all', 'thoughts', 'were', 'anxiously', 'directed', 'to', 'an', 'impending', 'civil', 'war', '.', 'All', 'dreaded', 'it', ',', 'all', 'sought', 'to', 'avert', 'it', '.', 'While', 'the', 'inaugural', 'address', 'was', 'being', 'delivered', 'from', 'this', 'place', ',', 'devoted', 'altogether', 'to', 'saving', 'the', 'Union', 'without', 'war', ',', 'urgent', 'agents', 'were', 'in', 'the', 'city', 'seeking', 'to', 'destroy', 'it', 'without', 'war—seeking', 'to', 'dissolve', 'the', 'Union', 'and', 'divide', 'effects', 'by', 'negotiation', '.', 'Both', 'parties', 'deprecated', 'war', ',', 'but', 'one', 'of', 'them', 'would', 'make', 'war', 'rather', 'than', 'let', 'the', 'nation', 'survive', ',', 'and', 'the', 'other', 'would', 'accept', 'war', 'rather', 'than', 'let', 'it', 'perish', ',', 'and', 'the', 'war', 'came', '.', 'One-eighth', 'of', 'the', 'whole', 'population', 'were', 'colored', 'slaves', ',', 'not', 'distributed', 'generally', 'over', 'the', 'Union', ',', 'but', 'localized', 'in', 'the', 'southern', 'part', 'of', 'it', '.', 'These', 'slaves', 'constituted', 'a', 'peculiar', 'and', 'powerful', 'interest', '.', 'All', 'knew', 'that', 'this', 'interest', 'was', 'somehow', 'the', 'cause', 'of', 'the', 'war', '.', 'To', 'strengthen', ',', 'perpetuate', ',', 'and', 'extend', 'this', 'interest', 'was', 'the', 'object', 'for', 'which', 'the', 'insurgents', 'would', 'rend', 'the', 'Union', 'even', 'by', 'war', ',', 'while', 'the', 'Government', 'claimed', 'no', 'right', 'to', 'do', 'more', 'than', 'to', 'restrict', 'the', 'territorial', 'enlargement', 'of', 'it', '.', 'Neither', 'party', 'expected', 'for', 'the', 'war', 'the', 'magnitude', 'or', 'the', 'duration', 'which', 'it', 'has', 'already', 'attained', '.']
  1. 请回想一下,单词标记化 只包括了截断的文本。然而,随后的频率信息和图表是针对整篇演讲的。ch15_historicalTextAnalysis.py GitHub 文件包含了完整的演讲:
<FreqDist with 365 samples and 782 outcomes>

以下屏幕截图显示了该算法的频率分布可视化图:

图 15.2 - 亚伯拉罕·林肯第二次就职演说的频率分布图

图 15.2 - 亚伯拉罕·林肯第二次就职演说的频率分布图

一旦我们有了这些信息,我们就可以开始更仔细地查看最常用的单词。在进行这种分析时,您可能希望考虑删除一些单词,比如toathe,因为它们对我们的分析没有意义。但是,像yearsUnion这样的单词可能与我们的分析相关。

这个算法可以进行许多调整,但目前,我们至少已经成功为一个历史演讲制作了一个频率分布图。现在,我们将继续下一个问题。

问题 2 - 使用 Python 编写故事

让我们看一个相当简单的问题。在这一部分,我们想要创建一个基于用户输入的算法来生成一个故事。我们可以尽可能简单,或者添加一些选项。但让我们深入了解一下这是什么。

定义、分解和规划故事

首先,我们要创造什么? 好吧,一个故事。由于这个问题的性质,我们将从反向开始,用我们想要实现的输出样本开始,也就是一个样本故事。在我们真正进入算法之前,让我们先看一下我们的算法生成的一个快速故事:

There once was a citizen in the town of Narnia, whose name was Malena. Malena loved to hang with their trusty dog, King Kong.
You could always see them strolling through the market in the morning, wearing their favorite blue attire.

前面的输出是由一个算法创建的,该算法替换了名字、地点、时间、宠物和宠物名字。这是一个简短的故事,但这是可以在更广泛的应用中使用的东西,比如使用输入来撰写社交媒体帖子,并为邀请、表格等填写信息。

所以让我们倒退一点来编写我们的算法。为什么这次我从结尾开始呢?嗯,在这种情况下,我们知道我们想要的结果。您可以编写您的故事。您可以有一个需要填写的婚礼邀请模板的示例,或者表格。现在我们必须弄清楚如何获取输入,然后输出我们想要的内容。

从所示的故事中,这是我们可以获得原始输入的事情:

  • 角色的名字

  • 城镇名称

  • 宠物的类型

  • 宠物的名字

  • 参观的城镇的一部分

  • 一天中的时间

  • 喜欢的颜色

当我们编写我们的算法时,我们需要获取所有上述输入。让我们来看看ch15_storyTime.py文件中的算法:

  1. 我们需要用户的输入,所以我们想使用一个print语句和包含所需指令的输入请求:
print('Help me write a story by answering some questions. ')
name = input('What name would you like to be known by? ')
location = input('What is your favorite city, real or imaginary? ')
time = input('Is this happening in the morning or afternoon? ')
color = input('What is your favorite color? ')
town_spot = input('Are you going to the market, the library, or the park? ')
pet = input('What kind of pet would you like as your companion? ')
pet_name = input('What is your pet\'s name? ')

前面的代码片段从用户那里获取所有输入,这样我们就可以写我们的故事了。

  1. 一旦我们有了这些,我们就必须print我们的故事。请注意,我们用简单的术语写了它,使用%s,这样我们就可以用相应的输入替换它。我们还使用反斜杠,这样我们就可以在多行上看到我们的代码,而不是将其放在一行上:
print('There once was a citizen in the town of %s, whose name was %s. %s loved to hang \
with their trusty %s, %s.' % (location, name, name, pet, pet_name))
print('You could always see them strolling through the %s \
in the %s, wearing their favorite %s attire.' % (town_spot, time, color))

让我们再次运行那段代码,看看我们的故事现在说了什么:

Help me write a story by answering some questions. 
What name would you like to be known by? Azabache
What is your favorite city, real or imaginary? Rincon
Is this happening in the morning or afternoon? afternoon
What is your favorite color? magenta
Are you going to the market, the library, or the park? library
What kind of pet would you like as your companion? dog
What is your pet's name? Luna
There once was a citizen in the town of Rincon, whose name was Azabache. Azabache loved to hang with their trusty dog, Luna.
You could always see them strolling through the library in the afternoon, wearing their favorite magenta attire.

请注意,角色和设置等细节已经发生了变化。在教育学习环境中,这样一个简单的算法可以成为一个很好的工具,用来向学生展示如何与故事互动并识别其中的关键信息。

虽然这是一个三句故事,但这些算法可以更加复杂,为用户输入提供了编写出色原创故事的机会。如果您想尝试一些内容,甚至可以根据一些输入条件更改使用的短语,例如根据输入的名称长度更改使用的句子。用代码和写一些故事来玩一玩吧!

问题 3-使用 Python 计算文本可读性

在这一部分,我们将看一个与语言学相关的应用,具体来说是任何文本的可读性水平。我们将在接下来的代码片段中使用马丁·路德·金的我有一个梦演讲。您可以用任何文本文件替换它,只要您在代码中准确反映文件和文件名的位置。完整的代码可以在ch15_Readability.py文件中找到。

在我们进入代码之前,让我们先谈谈我们要寻找的内容以及为什么它很重要。了解文本的可读性可以帮助我们决定是否将其包含在演示文稿中,学校年级水平等等。弗莱施-金凯德分数用于确定可读性,并于 1940 年代开发。

Rudolf Flesch 在与美联社合作期间创建了它,以改善报纸的可读性。最初被称为弗莱施阅读易度,它被现代化为目前美国海军使用的形式。现在,弗莱施-金凯德分数提供了一个年级水平分数,而不是得到一个分数,然后必须将其转换为年级水平。

虽然我们不会使用这个公式,但了解我们使用的背景很重要。弗莱施阅读易度公式如下:

弗莱施-金凯德年级公式如下:

前面的公式的事实是,它们存在于 Python 可用的可读性包中。如果我们导入这个包,我们就能用一小段相当简单的代码进行可读性分析。

让我们来看看我们需要执行马丁·路德·金演讲的可读性分析所需的代码:

  1. 首先,记得在代码中更改文件的路径,然后导入代码所需的必要包:

ch15_readability.py

from readability import Readability
text = open('C:\\...\\ch15_MLK-IHaveADream.txt')
text_up = text.read()
r = Readability(text_up)
flesch_kincaidR = r.flesch_kincaid()

从前面的代码中,您会看到我们将readability包导入到我们的程序中。如果您需要安装库/包,可以使用pip install readability来安装。

一旦我们有了必要的库,我们就可以打开要分析的文件。我们还希望告诉算法读取文本,我在这里称之为text_up,以便上传文本,这样我就不会忘记我正在读取一个打开的文件。这是我们从前面的代码中打开的文本。最后,我们要求程序使用Readability函数分析文本。请注意,我们将其保存到r中。

  1. 在我们完成所有这些之后,我们可以使用以下代码片段print我们的年级水平:
print('The text has a grade '+ flesch_kincaidR.grade_level + ' readability level.')

当我们运行我们的算法时,输出也是非常简单的。看一下以下输出:

The text has a grade 9 readability level.

现在您知道如何验证任何文本的可读性,尝试对其他类型的文本执行分析,包括诗歌、故事、演讲、歌曲等。

问题 4 - 使用 Python 找到最有效的路线

对于这个问题,在学习算法时,我们将使用一个常见的算法——旅行推销员问题TSP)。让我们来设置问题本身。

一个推销员需要前往一定数量的城市或地点。假设推销员有 10 个地点要去。他们可以以许多不同的顺序去这 10 个地点。我们这个算法的目标是创建最佳的、最有效的路线,以到达这些地点。

请注意,对于这种特定情况,就像我们将在下一个问题中所做的那样,我们将使用计算思维过程的四个元素进行简单的分析。

定义问题(TSP)

这个问题比起最初的样子要复杂一些。可以这样想。如果我们有 10 个目的地,我们正在计算往返排列以检查最快的路线,我们将得到超过 300,000 个可能的排列和组合。提醒一下,排列考虑顺序,而组合则不考虑。

例如,数字33443434是两种不同的排列。但是,它们只被计为一种组合,因为数字的顺序并不重要。

但回到我们的问题。我们只需要知道我们想要创建一个算法,以最有效的方式带我们到达目的地。我们必须确定要访问的城市,并确定我们将如何旅行。

  • 总共有五个城市,分别是纽约NYC)、费城巴尔的摩芝加哥克利夫兰

  • 我们将使用一辆车,因为我们使用的是 TSP 而不是车辆路径问题VRP)。

  • 第一个城市是 0,也就是纽约。纽约和自身之间的距离是 0。

现在让我们来看一下模式。

识别模式(TSP)

对于每个城市,将有五个距离,到自身的距离等于 0。我们将需要一个数组或列表,用于存储每个城市的所有距离。我们需要创建一个模型,以便在我们的算法中访问数据。我们将在设计算法时进行讨论。首先,让我们稍微概括一下模式。

概括(TSP)

对于这个特定的问题,我们将手动将城市输入到算法中。您可能需要考虑的一件事是如何从用户那里获取输入,以便创建必要的包含距离的数组。

您还可以创建一个数据库,记录主要城市之间的距离,可以从.csv文件中访问,这样输入的城市数据可以在其中找到,然后添加到我们的模型中。对于这个特定算法,有许多补充,这不是一个只有一种解决方法的问题。现在,我们将使用一组定义好的城市,以便创建我们的算法。

顺便说一句,我们已经参考了来自developers.google.com/optimization/routing/tsp的源代码。

设计算法(TSP)

是时候看看我们一直在谈论的东西了。让我们从纽约开始,首先构建那个数组。其他数组以相同的方式创建。所有距离都是以英里为单位,并根据Google Maps的数据进行了近似和四舍五入,如下所示:

  • 从纽约到纽约的距离是 0。

  • 从纽约到费城的距离是 95。

  • 从纽约到巴尔的摩的距离是 192。

  • 从纽约到芝加哥的距离是 789。

  • 从纽约到克利夫兰的距离是 462。

以下表格显示了每个城市到其他城市及其自身的距离:

表 15.1 - 从一个城市到另一个城市的距离

表 15.1 - 从一个城市到另一个城市的距离

因此,如您在上表中所见,如果我们将这些距离写成一个数组,我们将使用以下代码:

[0, 95, 192, 789, 462]

对于费城,我们将有以下数组:

[95, 0, 105, 759, 431]

对于巴尔的摩,我们将有以下数组:

[192, 105, 0, 701, 374]

对于芝加哥,我们将有以下数组:

[789, 759, 701, 0, 344]

最后,对于克利夫兰,我们将有以下数组:

[462, 431, 374, 344, 0]

请注意,我们将为每个城市分配索引以便识别它们。纽约是0,费城是1,巴尔的摩是2,芝加哥是3,克利夫兰是4。让我们看看这个问题的算法是什么样子的(请注意,OR-Tools 库用于优化车辆路线、线性规划、约束规划等):

  1. 首先,让我们开始导入我们需要的包和库。这个算法的完整文件是ch15_travel.py,可以在 GitHub 上找到:
from ortools.constraint_solver import routing_enums_pb2
from ortools.constraint_solver import pywrapcp

请记住,如果您计划访问更多的城市和/或不同的城市,这个算法需要获取一个新的距离矩阵。这是您需要更改的代码的唯一部分。您每次需要调整的代码片段是create_data_model()下的矩阵,如下面的代码片段所示:

#Create data model.
def create_data_model():
    data = {}
    data['distance_matrix'] = [
        [0, 95, 192, 789, 462],
        [95, 0, 105, 759, 431],
        [192, 105, 0, 701, 374],
        [789, 759, 701, 0, 344],
        [462, 431, 374, 344, 0],
    ]  
    data['num_vehicles'] = 1
    data['depot'] = 0
    return data
  1. 在我们定义了数据模型之后,我们需要打印一个解决方案。以下函数提供了该信息:
#Provide solution as output - print to console
def print_solution(manager, routing, solution):
    print('Objective: {} miles'.format(solution.ObjectiveValue()))
    index = routing.Start(0)
    plan_output = 'Route for vehicle 0:\n'
    route_distance = 0
    while not routing.IsEnd(index):
        plan_output += ' {} ->'.format(manager.IndexToNode(index))
        previous_index = index
        index = solution.Value(routing.NextVar(index))
        route_distance += routing.GetArcCostForVehicle(previous_index, index, 0)
    plan_output += ' {}\n'.format(manager.IndexToNode(index))
    print(plan_output)
    plan_output += 'Route distance: {}miles\n'.format(route_distance)

如您从上述代码中所见,我们正在创建一个函数,以便我们可以根据我们的数组和这些数组中的距离打印解决方案。请记住,您将确定出发点,也就是您要离开的城市。然后我们运行算法来收集信息并创建我们的print语句。

  1. 最后,我们需要定义我们的main()函数以运行我们的算法。main()函数告诉算法继续创建我们已经定义的数据模型,并将其存储为数据。然后我们创建路由模型来找到我们的解决方案。请看以下代码片段:
def main():    
    data = create_data_model()
    manager = pywrapcp.RoutingIndexManager(len(data['distance_matrix']),
                                           data['num_vehicles'], data['depot'])
    # Create Routing Model.
    routing = pywrapcp.RoutingModel(manager)
    def distance_callback(from_index, to_index):
        """Returns the distance between the two nodes."""
        # Convert from routing variable Index to distance matrix NodeIndex.
        from_node = manager.IndexToNode(from_index)
        to_node = manager.IndexToNode(to_index)
        return data['distance_matrix'][from_node][to_node]
    transit_callback_index = routing.RegisterTransitCallback(distance_callback)
    routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)
    search_parameters = pywrapcp.DefaultRoutingSearchParameters()
    search_parameters.first_solution_strategy = (
        routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)
    solution = routing.SolveWithParameters(search_parameters)
    if solution:
        print_solution(manager, routing, solution)
if __name__ == '__main__':
    main()

上述代码显示了我们如何定义我们的main()函数。需要注意的是,main()函数可以被命名为任何我们想要的名称。在使用多个函数时,我们有时使用main()来标识最初从算法中输出我们想要的功能。对于这个问题,我们正在创建一个main()函数,它将确定我们旅行的最佳路线。

  1. 现在让我们看看当我们运行这段代码时,我们得到了什么输出。该代码为我们提供了里程的“目标”总数以及我们应该采取的行程路线。以下是输出:
Objective: 1707 miles
Route for vehicle 0:
 0 -> 1 -> 2 -> 4 -> 3 -> 0

从纽约到纽约的行程,如果我们按照以下顺序进行,将会是最有效的:纽约 | 费城 | 巴尔的摩 | 克利夫兰 | 芝加哥 | 纽约。

这不是旅行问题的唯一方法。如果我们想要一天运行多次这个问题,或者为不同的旅行者运行,这也不一定是最用户友好的方法。为了做到这一点,您需要自动化更多的内容,就像前面的例子中提到的那样。您可能要考虑的一些事情如下:

  • 能够输入城市

  • 拥有一个抓取信息以确定距离的计算器

  • 使用自动化过程创建距离矩阵

但是现在,你已经看到 TSP 在行动中了!我们将在下一节中看一个新问题。

问题 5 – 使用 Python 进行密码学

密码学是我们用来编码和解码消息的工具。在第九章中,我们使用了一个简单的凯撒密码理解输入和输出以设计解决方案算法。对于这个问题,我们将使用 Python 中可用的一些包来加密和解码信息。

请注意,对于这种特定情况,我们将使用计算思维过程的四个元素进行直接分析。虽然我们并不总是完全遵循它们,但这个特定问题本身就很适合进行相当直接的使用。

定义问题(密码学)

您正在从事一个机密项目,需要加密您的信息以保证其安全性。

识别模式(密码学)

Python 有一个可以安装的密码学包,就像我们安装其他库(如PandasNumPy)一样。在我们的问题中,我们需要知道的主要事情之一是,我们可能需要继续加密消息。我们可能还想解码我们收到的消息,但我们首先要专注于加密方面。

泛化(密码学)

当我们设计我们的算法时,我们希望能够在项目的整个生命周期中持续使用某些东西,而不需要太多的努力。也就是说,每当我们想要加密新消息时,我们可以运行算法并输入消息,而不是每次都将消息本身添加到算法体中。这是我们特定问题的通用模式。这意味着我们已经准备好设计了。

设计算法(密码学)

在编写我们的算法之前,让我们首先看一下我们需要做的事情:

  1. 定义字母。

  2. 将所有字母改为小写以运行我们的算法。

  3. 定义所需的函数——加密解码main

  4. 调用密码学的main函数。

提示:

这个问题的完整算法可以在ch15_cryptographyA.py文件中找到。

我们将按照以下步骤开始设计我们的算法:

  1. 让我们首先定义我们的字母。下面的代码片段定义了我们的字母,然后将每个字母转换为小写:
LETTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ'
LETTERS = LETTERS.lower()
  1. 接下来,我们定义我们的加密函数。这个函数将接受两个参数——messagekeymessage函数将由用户定义,在main函数中完成。现在,我们将通过在encryptedM变量的定义中添加空引号('')来使用空消息,如下面的代码片段所示:
def encrypt(message, key):
    encryptedM = ''
    for letts in message:
        if letts in LETTERS:
            num = LETTERS.find(letts)
            num += key
            encryptedM +=  LETTERS[num]
    return encryptedM

请注意,我们迭代要加密的消息中的字母,然后我们使用用户在main函数中定义的密钥来加密消息。然后这个函数返回加密的消息。

但是为什么我们还没有定义 main 函数,如果那是我们获取输入的地方?因为main函数将需要另外两个函数来加密或解码消息。所以请耐心等待;我们很快就会到达main函数。

  1. 现在让我们来看一下解码函数。当我们有一个加密的消息并想知道原始消息是什么时,我们将使用这个函数:
def decode(message, key):
    decodedM = ''
    for chars in message:
        if chars in LETTERS:
            num = LETTERS.find(chars)
            num -= key
            decodedM +=  LETTERS[num]
    return decodedM

前面的代码显示了我们将用来解码消息的函数。它使用消息中的字符和加密密钥来解码消息。请注意,如果没有原始密钥,您就无法解码消息,除非您有时间坐下来尝试每一个密钥。

  1. 最后,我们需要那个我们一直在提到的main函数。这个函数需要所有必要的输入,以便这个算法能够正确运行。以下是它正确运行所需的三件事情——要加密或解码的消息;密钥,可以是 1 到 26 范围内的任何数字;以及我们是在加密还是解码。

这是main函数:

def main():
    message = input('What message do you need to encrypt or decrypt? ')
    key = int(input('Enter the key, numbered 1-26: '))
    choice = input('Do you want to encrypt or decode? Type E for encrypt or D for decode: ')
    if choice.lower().startswith('e'):
        print(encrypt(message, key))
    else:
        print(decode(message, key))
if __name__ == '__main__':
    main()

从前面的代码中可以看到,我们定义了一个main函数。在代码的结尾,我们调用了该函数。不要忘记在算法中调用main函数!这是让算法运行的方法。

当我们尝试使用密钥9加密输入消息the name of the dog is King Kong时,这是一个样本输出:

What message do you need to encrypt or decrypt? the name of the dog is King Kong
Enter the key, numbered 1-26: 9
Do you want to encrypt or decode? Type E for encrypt or D for decode: E
cqnwjvnxocqnmxprbrwpxwp

正如您所看到的,我们获得了加密文本cqnwjvnxocqnmxprbrwpxwp作为密文,现在我们已经创建了一个可以加密或解密任何消息的算法。现在让我们继续解决一个新问题。

问题 6–在网络安全中使用 Python

对于这个问题,我们决定进行一个相当简短的网络安全检查。首先,让我们谈谈网络安全。根据Grand View Research的报告,预计到 2027 年,网络安全市场将增长 10%。

将这些内容转化为就业市场有点棘手。例如,在美国,网络安全的需求比市场上的人员或工作岗位更多。预计到 2028 年,该岗位市场增长率将略高于 30%。因此,学习一些关于网络安全和密码学的知识是有益的。

对于这个特定的问题,我们将探讨一些事情。首先,让我们谈谈哈希。在网络安全中,哈希意味着那些用数字和字母组成的非常长的字符串,用来替代密码之类的东西。例如,如果您输入了密码password1(请不要这样做,永远不要使用password作为密码),哈希过程会将其替换为看起来更像这样的东西:

27438d623d9e09d7b0f8083b9178b5bb8ff8bc321fee518af 4466f6aadb68a8f:100133bfdbff492cbc8f5d17af46adab

当我们创建密码算法时,我们必须添加随机数据,我们称之为。盐只是提供额外的输入,并在存储密码时帮助我们使密码更安全。

在 Python 中使用哈希时,我们可以使用uuid库。UUID代表通用唯一标识符。当我们想要生成随机的 128 位对象作为 ID 时,就会使用uuid库。但我们到底在谈论什么? 让我们来看看ch15_hashing.py文件中的算法:

  1. 首先我们要导入库:
import uuid
import hashlib

我们正在导入两个库,这将允许我们使用加盐和哈希保存我们的密码。

  1. 在文件的下一个代码片段中,我们定义了对密码进行哈希的函数:
def hash_pwd(password):
    salt = uuid.uuid4().hex 
    return hashlib.sha1(salt.encode() + password.encode()).hexdigest() + ':' + salt

我们使用我们的uuid包对密码进行了加盐处理,然后使用安全哈希算法 1 sha1返回了哈希值。这只是我们可以使用的算法之一。我们还可以使用其他算法,比如SHA-256SHA-384等。sha1哈希的输出大小为 160,而sha256的输出大小为 256。sha1sha256的块大小都为 512 位,而sha384的块大小为 1,024 位。

在选择我们将使用的哈希以及它们的安全性等方面,所有这些都变得相关。我们在这里使用sha1更多是出于怀旧,但它不像sha256sha384那样安全。在受到攻击时,sha1将无法抵御长时间的攻击。另外两者能够更长时间地抵挡,但仍然不是最好的选择。像shake128shake256这样的哈希对抗这种攻击更成功。

  1. 现在让我们看一下check函数。我们总是希望通过要求两次输入密码来确认密码。以下代码片段定义了当接收到第二个密码时算法将执行的操作:
def check_pwd(hashed_pwd, user_pwd):
    password, salt = hashed_pwd.split(':')
    return password == hashlib.sha1(salt.encode() + user_pwd.encode()).hexdigest()
  1. 现在让我们要求一些输入。首先,我们会要求输入密码。因为我们对程序的操作很感兴趣,所以我们会打印出哈希密码,但在构建成网站或其他应用程序时,您可以省略该行。之后,我们要求验证密码,并为用户提供输出,以便他们知道它们是否匹配,如果匹配,我们可能会希望他们再试一次。目前,该算法要么确认它,要么让用户知道它现在已经确认:
new_pwd = input('Enter new password: ')
hashed_pwd = hash_pwd(new_pwd)
print('Hashed password: ' + hashed_pwd)
confirm_pwd = input('Confirm password: ')
if check_pwd(hashed_pwd, confirm_pwd):
    print('Confirmed!')
else:
    print('Please try again')

运行程序后,我们得到以下输出:

图 15.3–加盐和哈希密码确认的输出

图 15.3–盐化和哈希密码确认的输出

如你从上述截图中所见,密码被系统确认了。

  1. 现在让我们看看当我们输入两个不同的密码时会发生什么。让我们看一下以下的截图:图 15.4–带有确认失败的盐化和哈希密码的输出

图 15.4–带有确认失败的盐化和哈希密码的输出

如你所见,程序要求用户再试一次。然而,除非重新启动过程,算法并没有提供这样的方式。我们可以让它保持这样,或者我们可以添加条件,使程序再次运行一次,或者两次,或者无限次,直到确认达成为止。

  1. 现在让我们看看如果我们使用sha256而不是sha1来运行算法会发生什么。以下截图显示了使用sha256确认密码时的结果:图 15.5–在确认密码的算法中用 sha1 替换 sha256 时的输出

图 15.5–在确认密码的算法中用 sha256 替换 sha1 时的输出

请注意哈希值在sha256算法方面的长度更长。在使用密码学时,随机和长总是有帮助的。破解一个不随机的密码,比如passwordmycat,要比破解一个非常长并包含随机数字和字母的密码更容易。这就是为什么我们试图以能够保护数据免受攻击的方式存储数据。

  1. 让我们看看我们可以做些什么来为某人提供再次输入密码的机会。在算法结束时,让我们在最后一行之后添加一些代码:
    new_pwd = input('Enter new password: ')
    hashed_pwd = hash_pwd(new_pwd)
    print('Hashed password: ' + hashed_pwd)
    confirm_pwd = input('Confirm password: ')
    if check_pwd(hashed_pwd, confirm_pwd):
        print('Confirmed!')
    else:
        print('Please try again later')

请注意我们之前片段中的最后一句话是“请稍后重试”。这让用户知道,如果他们想要保存密码,他们将不得不重新开始这个过程。算法在那时停止了。

  1. 如果我们将上述代码放在我们的elseprint()语句之后,那么算法将再运行一次。以下截图显示了用户再次尝试时的输出:

图 15.6–在首次运行算法时输出不匹配后

图 15.6–在首次运行算法时输出不匹配后

在我们从这个例子中继续之前,请注意,尽管我们两次输入的新密码都是test,但提供的哈希密码是不同的。正如我们提到的,哈希密码是每次创建的。否则,每个人都会知道密码是如何存储的,因为只要我们使用相同的哈希,比如sha256test就会是相同的。

在网络安全和密码学中还有很多可以探索的内容。这只是我们如何加密信息的一个味道。

问题 7–使用 Python 创建一个聊天机器人

是时候创建一个简单的聊天机器人了。在过去几年里,你可能至少与十几个这样的聊天机器人互动过。当你访问一些网站时,你可能会遇到一个“人”想和你聊天,并问你一些简单的问题,比如你最近怎么样,以及他们能帮你什么。对于大多数网站来说,“人”并不是真人,而是一个聊天机器人。

在某些情况下,聊天机器人会将你引导到一个真正的人。但大多数时候,它们只会通过指向他们网站上可用答案的方向来回答你的问题。

我们将在这里创建类似那些聊天机器人的东西。在我们开始之前,有一些组件是我们需要的。其中之一是一个intents文件。这个文件应该是一个.json文件,包含了机器人将使用和/或回应的问候和回应。以下是intents内容的样本:

{"intents": [
        {"tag": "greeting",
         "patterns": ["Hi", "How are you", "Hello?", "Welcome!", "Hello"],
         "responses": ["Hello! Thank you for visiting our site! ", "Welcome back!", "Hello, how can I help you?", "What can I do for you? "],
         "context_set": ""
        },
        {"tag": "goodbye",
         "patterns": ["Bye", "See you later", "Goodbye"],
         "responses": ["See you later, thanks for visiting", "Thank you and have a wonderful day!", "Bye! See you soon!"]
        }
   ]
} 

正如您所看到的,这只是一组可能的响应。我们给.json文件提供的数据越多,我们的机器人就会越强大和准确。

我们应该注意,intents.json文件需要在JSON 编辑器中进行编辑。您可以在jsoneditoronline.org上使用一个在线编辑器来创建自己的文件,或者编辑现有的文件。

我们为什么需要一个机器人? 这样的东西有各种各样的用途,从创建和发布社交媒体上的消息到为客户提供一个询问他们是否需要帮助的机器人,例如当他们访问网页。这些只是我们可以用聊天机器人做的一些事情。

现在让我们来看一个创建聊天机器人的算法。完整的文件可以在存储库中找到。我们已经在一些片段中进行了注释和描述了发生的事情:

  1. 让我们从这里导入库开始:

ch15_chatBot.py

import nltk
import json
import pickle
import numpy as np
nltk.download('punkt')
nltk.download('wordnet')
from nltk.stem import WordNetLemmatizer
lemmatizer = WordNetLemmatizer()

从前面的代码中可以看出,您不必每次下载nltk模块。但是拥有这段代码也不会有害。系统不会每次安装多个副本;它只会识别它们已经存在,并且不会第二次安装它们。

  1. 让我们继续从我们的库和软件包中获取我们需要的东西:
from keras.models import Sequential
from keras.optimizers import SGD
from keras.layers import Activation, Dense, Dropout
import random
  1. 现在我们已经得到了我们需要的东西,我们必须查看我们的.json文件。该文件包含了前面提到的意图。我们不仅需要打开该文件,还需要将组件分开并以我们的算法能够理解的方式进行排序。看一下以下代码片段:
#Upload intents file and create our lists
words=[]
classes = []
doc = []
ignore_words = ['?', '!', ',', '.']
data_words = open(r'C:\...\intents.json').read()
intents = json.loads(data_words)

请记住,除非您指定要访问的文件的正确位置,否则程序将无法运行,在这种情况下是.json文件。还要注意,这次我们以稍微不同的方式打开它,如前面的代码片段所示。以这种方式打开文件,与我们用 Pandas 打开.csv文件不同,意味着我们不需要在路径中使用双\\

  1. 现在让我们告诉算法如何处理该文件:
for intent in intents['intents']:
    for pattern in intent['patterns']:
        #Tokenize all the words (separate them)
        w = nltk.word_tokenize(pattern)
        words.extend(w)
        #Add all the words into doc 
        doc.append((w, intent['tag']))
        #Add the classes
        if intent['tag'] not in classes:
            classes.append(intent['tag'])
print(doc)      

在这里,我们正在对我们的信息进行标记化,也就是说,我们正在将所有内容分解成单词,然后将它们添加到列表中。这就是使处理信息成为可能的东西。在我们将它们分开后,我们将根据单词的含义对它们进行分组。

  1. 然后这些单词被排序,如下面的代码片段所示:
#lemmatization      
words = [lemmatizer.lemmatize(w.lower()) for w in words if w not in ignore_words]
words = sorted(list(set(words)))
classes = sorted(list(set(classes)))
pickle.dump(words,open('words.pkl','wb'))
pickle.dump(classes,open('classes.pkl','wb'))       

请注意,在前面的代码中,我们使用了pickle()。Pickle 是 Python 中的一个方法,我们可以使用它来序列化数据(或反序列化)。然后使用该方法替换当前文件数据,以便可以将其用作转换。

  1. 现在我们已经完成了所有这些,我们需要创建我们的训练模型。我们不会在这里逐个讨论该过程的所有部分,但整个代码可以在 GitHub 存储库文件中找到。请记住,您首先要训练,然后创建,然后编译模型。

一旦我们完成了这个过程,我们就会保存模型以便我们可以使用它。但现在让我们看看聊天机器人的功能:

#Define chatbot functions
def clean_up_sentence(sentence):
    sentence_words = nltk.word_tokenize(sentence)
    sentence_words = [lemmatizer.lemmatize(word.lower()) for word in sentence_words]
    return sentence_words
def bow(sentence, words, show_details=True):
    sentence_words = clean_up_sentence(sentence)
    bag = [0]*len(words)
    for s in sentence_words:
        for i,w in enumerate(words):
            if w == s:
                bag[i] = 1
    return(np.array(bag))
def predict_class(sentence, model):
    p = bow(sentence, words,show_details=False)
    res = model.predict(np.array([p]))[0]
    ERROR_THRESHOLD = 0.25
    results = [[i,r] for i,r in enumerate(res) if r>ERROR_THRESHOLD]
    results.sort(key=lambda x: x[1], reverse=True)
    return_list = []
    for r in results:
        return_list.append({"intent": classes[r[0]], "probability": str(r[1])})
    return return_list

前三个函数用于为聊天机器人创建响应并进行预测。这些类将影响我们如何从我们的机器人那里获得这些响应。让我们这样考虑——如果我说你好,我不希望聊天机器人说再见。那将是不礼貌的。但请记住,我们的机器人只有我们的训练好,它才会有多好。因此,如果我们在.json文件中没有足够的内容,并且没有正确训练模型,那么机器人将是相当无用的。

  1. 现在让我们定义如何获取响应:
def getResponse(ints, intents_json):
    tag = ints[0]['intent']
    list_of_intents = intents_json['intents']
    for i in list_of_intents:
        if(i['tag']== tag):
            result = random.choice(i['responses'])
            break
    return result
def chatbot_response(msg):
    ints = predict_class(msg, model)
    res = getResponse(ints, intents)
    return res

从前面的代码片段中可以看到,机器人将制作一个响应并返回它。我们将在我们的文件中的下几个代码片段中调用这些内容。

  1. 但我们将在这里跳过这一点,进入我们聊天机器人的外观:
base = Tk()
base.title("Chat with Customer Service")
base.geometry("400x500")
base.resizable(width=FALSE, height=FALSE)

请注意,我们在前面的代码片段中建立了一些关键信息。我们确定了窗口的大小并阻止了其调整大小。

  1. 在下一个代码片段中,我们将建立背景,添加滚动条,并确定发送按钮的外观:
#Create chatbot window
ChatLog = Text(base, bd=6, bg="white", height="8", width="70", font="Calibri")
ChatLog.config(state=DISABLED)
#Scrollbar
scrollbar = Scrollbar(base, command=ChatLog.yview, cursor="arrow")
#Create Send button
SendButton = Button(base, font=("Calibri",12,'bold'), text="Send", width="15", height=5,
                    bd=0, bg="pink", activebackground="light green",fg='black',
                    command= send )
EntryBox = Text(base, bd=0, bg="white",width="29", height="5", font="Arial")
scrollbar.place(x=376,y=6, height=386)
ChatLog.place(x=6,y=6, height=386, width=370)
EntryBox.place(x=128, y=401, height=90, width=265)
SendButton.place(x=6, y=401, height=90)
base.mainloop()

所以,这就是全部。我们创建了一个聊天机器人! 但是当它运行时会是什么样子呢? 看一下输出:

图 15.7 - 聊天机器人窗口

图 15.7 - 聊天机器人窗口

注意我们的组件,左侧的滚动条,粉色的发送按钮,以及我们聊天机器人的标题。还要注意最大化按钮是灰色的。这是因为我们说过我们不希望窗口被调整大小。

  1. 此外,当我们点击发送按钮时,我们希望用户知道它是否被点击。否则,如果不确定,您可能会多次点击它。这就是为什么代码中活动的背景颜色会改变。下面的截图显示了按钮在活动状态下的外观:

图 15.8 - 活动的发送按钮

图 15.8 - 活动的发送按钮

许多聊天机器人都有类似的功能,以避免代码错误。

一旦我们打招呼,机器人就会回应。在我们离开这个问题之前,让我们看一下下面截图中与聊天机器人的快速对话:

图 15.9 - 带有响应的聊天机器人窗口

图 15.9 - 带有响应的聊天机器人窗口

正如您所看到的,几行代码和一个文件可以用来创建与聊天机器人的交互体验。

随意玩弄代码,添加一些特色,创建一个不同的intents.json文件,并使其更符合您的需求。

总结

在本章中,我们有机会在查看实际问题的同时,探索 Python 在一些非常不同的应用中的应用。

在之前的章节中,我们学习了计算思维过程,以及分解、模式识别、模式概括和算法设计的要素,这些使算法有意义。当我们解决来自客户的问题或者只是在业余时间创建脚本时,我们必须经历必要的过程来定义我们用算法创建的东西。这个关键过程将确保我们设计出尽可能好的算法。

在本章中,我们学会了如何从文件中读取、上传文件、创建密码和解码器、使用算法根据用户输入编写故事,并在给定我们将访问的城市时制定最有效的旅行计划。此外,我们还创建了一个基本的聊天机器人,它可以根据用户输入进行交互和适应。

在下一章中,我们将继续探索 Python 和计算思维,在科学应用、房地产、股票市场分析等领域中解决数据分析的附加应用问题。

第十六章:高级应用计算思维问题

在本章中,我们将继续提供 Python 编程语言和计算思维在多个领域的应用示例。我们将探索多个领域,如几何镶嵌、创建房屋数据模型、创建电场、分析基因数据、分析股票、创建卷积神经网络(CNN)等。我们将利用我们迄今为止学到的关于计算思维和 Python 编程语言的知识来做以下事情:

  • 创建镶嵌

  • 分析生物数据

  • 分析特定人群的数据

  • 创建房屋数据模型

  • 创建电场线

  • 分析通用数据

  • 分析股票

  • 创建卷积神经网络(CNN)

阅读本章后,您将学会如何在处理数据、创建表格和图形以帮助分析现有数据的同时,创建训练和测试模型以帮助根据现有大型数据集预测结果。

技术要求

您需要安装最新版本的 Python 来运行本章中的代码。

您需要为 Python 安装以下库和包:

  • NLTK

  • Cairos

  • Pandas

  • Matplotlib

  • Seaborn

您可以在此处找到本章中使用的完整源代码:github.com/PacktPublishing/Applied-Computational-Thinking-with-Python/tree/master/Chapter16

问题 1 - 使用 Python 创建镶嵌

在本节中,我们将使用 Python 的cairo库提供一个示例。我们将创建一个镶嵌,更具体地说,是一个彭罗斯镶嵌的样本。因为这是一个简单的问题,我们将使用计算思维过程来定义我们的参数,但不会严格遵循它。

首先,让我们谈谈cairo库。一旦pip install cairo命令成功,您需要执行一个步骤来包含所有需要的组件。使用pip install pycairo来添加必要的组件。cairopycairo包是与 Python 一起使用的图形库。有关更多信息,您可以访问它们的网页:cairographics.org/pycairo

现在让我们定义一些东西。镶嵌是使用不重叠的形状进行平铺以创建图案。镶嵌经常在几何课程中探索。在我们的示例中,我们将使用两个三角形创建一个彭罗斯镶嵌图案。我们还将定义我们的空间和我们希望形状经历的子细分数。子细分数越多,空间中的图案就越小。让我们看一下算法(文件ch16_tessellation.py包含了这里讨论的完整算法):

  1. 我们要做的第一件事是导入必要的包和库:

ch16_tessellation.py

import math
import cmath
import cairo
  1. 接下来,我们要定义我们的画布和子细分数。请注意,我们选择了4作为我们的示例。在图 16.1中,您将看到来自此代码片段的示例以及改变子细分的两个额外示例:
#Define the configuration of the image. 
canvas_size = (500, 500)
numberSubdivisions = 4
  1. 对于镶嵌,我们需要定义黄金比例

黄金比例也被称为黄金平均或神圣比例(还有其他名称)。该比例约为 1.618。例如,如果我们谈论将一条线段分成两部分,那么较大部分的长度除以较小部分的长度将等于两部分之和除以较大部分的长度:。对于镶嵌,我们需要定义这个黄金比例。

看一下以下代码片段:

#Define the Golden Ratio - gr
gr = (1 + math.sqrt(5)) / 2
  1. 现在我们使用函数来定义当我们的三角形进行子细分时会发生什么:
def subdivide(triangles):
    result = []
    for color, A, B, C in triangles:
        if color == 0:
            P = A + (B - A) / gr
            result += [(0, C, P, B), (1, P, C, A)]
        else:
            Q = B + (A - B) / gr
            R = B + (C - B) / gr
            result += [(1, R, C, A), (1, Q, R, B), (0, R, Q, A)]
    return result

在上述代码中,我们定义了将三角形细分的函数。该函数包含一个条件语句,用于在找到比例之前识别三角形的颜色。

  1. 要创建三角形的轮廓,我们需要将三角形附加到一个组中。因为 Python 是一种面向对象的编程语言,所以我们可以通过创建一个空列表,然后使用循环附加形状来轻松地做到这一点:
#Wheel of teal triangles
triangles = []
for i in range(10):
    B = cmath.rect(1, (2*i - 1) * math.pi / 10)
    C = cmath.rect(1, (2*i + 1) * math.pi / 10)
    if i % 2 == 0:
        B, C = C, B  # Make sure to mirror every second triangle
    triangles.append((0, 0j, B, C))
for i in range(numberSubdivisions):
    triangles = subdivide(triangles)
  1. 现在我们要准备用于我们的镶嵌的画布。请注意,我们使用cairo函数来使用我们在算法开头定义的变量来定义参数。在这里,我们使用canvas_size
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, canvas_size[0], canvas_size[1])
cr = cairo.Context(surface)
cr.translate(canvas_size[0] / 2.0, canvas_size[1] / 2.0)
wheelRadius = 1.2 * math.sqrt((canvas_size[0] / 2.0) ** 2 + (canvas_size[1] / 2.0) ** 2)
cr.scale(wheelRadius, wheelRadius)
  1. 现在我们要定义我们将使用的两个三角形。在这种情况下,我们的三角形是蓝绿色和紫色的,但是您可以更改它们的 RGB 值,也就是说,如果您想要测试不同的颜色:
#Define the teal triangles
for color, A, B, C in triangles:
    if color == 0:
        cr.move_to(A.real, A.imag)
        cr.line_to(B.real, B.imag)
        cr.line_to(C.real, C.imag)
        cr.close_path()
cr.set_source_rgb(.2, .8, .8)
cr.fill()    
#Define the purple triangles
for color, A, B, C in triangles:
    if color == 1:
        cr.move_to(A.real, A.imag)
        cr.line_to(B.real, B.imag)
        cr.line_to(C.real, C.imag)
        cr.close_path()
cr.set_source_rgb(0.7, 0, 0.7)
cr.fill()
color, A, B, C = triangles[0]
cr.set_line_width(abs(B - A) / 10.0)
cr.set_line_join(cairo.LINE_JOIN_ROUND)

上述代码创建了蓝绿色的三角形和紫色的三角形。每个都使用 RGB 值定义,并使用路径和线条创建。

  1. 旋转的三角形形成了一个瓷砖图案,也就是我们的镶嵌图案。瓷砖之间也有边框分隔。边框的颜色也在接下来的循环中定义:
#Triangle borders
for color, A, B, C in triangles:
    cr.move_to(C.real, C.imag)
    cr.line_to(A.real, A.imag)
    cr.line_to(B.real, B.imag)
cr.set_source_rgb(0.3, 0.5, 0.3)
cr.stroke()
  1. 最后,我们希望算法创建一个带有我们的镶嵌的图像文件:
surface.write_to_png('tessellation.png')

以下截图显示了使用不同数量的子区划进行的三种变化:

图 16.1 – 样本镶嵌

图 16.1 – 样本镶嵌

正如您从前面的图像中所看到的,子区划的数量越多,瓷砖图案就会变得越小,以适应我们定义的画布大小。

当您使用算法时,考虑更改画布大小、子区划和颜色。如果您想要额外的挑战,尝试将三角形图案更改为其他多边形。

问题 2 – 在生物数据分析中使用 Python

对于这个特定的问题,我们将使用Breast_cancer_data.csv文件,该文件可以在Kaggle上找到(www.kaggle.com/nsaravana/breast-cancer?select=breast-cancer.csv)。该文件也已上传到本书的 GitHub 存储库中。

当查看数据时,有时我们想要与我们当前拥有的数据进行比较,或者我们想要在机器学习中使用它进行预测。在这种情况下,我们将看看如何使用数据集中两个特定列的值来呈现另一种类型的图表,即散点图

假设您收到了这些数据,并且已经确定您的平均周长和平均纹理比列中的其他值更好。您现在的目标是创建一个算法,通过比较这两列的值来分析这两列的值,使用散点图。我们的目标只是获得那个散点图。对于额外的分析和机器学习应用,可以自由探索[第十三章],使用分类和聚类,和[第十四章],在统计分析中使用计算思维和 Python,以获取额外的帮助。

这个问题的完整代码可以在文件ch16_BreastCancerSample.py中找到。我们现在可以开始设计我们的算法:

  1. 我们像往常一样从数据开始,导入我们将使用的库。请注意,我们在这里使用了两个显示库,即matplotlibseaborn库。这是我们第一次使用seaborn。我们使用seaborn是因为使用这个库可以轻松处理额外的工作,比如找到回归线:
#Import libraries needed
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline
  1. 现在我们要找到.csv文件。请记住,您可以先建立目录。否则,请确保您在运行文件之前包含文件的完整位置。由于我们的目录不同,请确保在运行文件之前更改目录:
#Get data set. Remember to check your directory and/or add the full location of the file.
dataset = pd.read_csv('C:\\... \\breast-cancer.csv')
dataset.head()
dataset.isnull().sum()

注意算法中的dataset.head()命令。如果我们仅运行代码到那一点,那么我们会得到以下输出:

图 16.2 - 显示标题数值的表格

图 16.2 - 显示标题数值的表格

dataset.isnull().sum()命令帮助我们查看是否有空数据点或值。

  1. 如果有许多空值,我们可以在开始分析之前清理数据集。这个数据是干净的,可以从以下输出中看到,如果我们运行程序直到dataset.isnull().sum(),如下屏幕截图所示:图 16.3 - 空值检查的输出

图 16.3 - 空值检查的输出

  1. 由于没有缺失值,如前面的屏幕截图所示,我们将继续到下一个代码片段,我们将为诊断创建count变量:
#Create count variable for diagnosis
count = dataset.diagnosis.value_counts()
count

在前面的代码片段中创建了count变量,这意味着我们可以使用诊断的值来创建条形图,无论是恶性还是良性。

  1. 以下代码片段创建了该条形图并显示了结果输出:
#Create bargraph of the diagnosis values
count.plot(kind = 'bar')
plt.title('Tumor distribution (malignant: 1, benign: 0)')
plt.xlabel('Diagnosis')
plt.ylabel('count')
plt.show()

看一下下面的屏幕截图,显示了使用诊断数值的条形图。正如你所看到的,条形图显示了恶性肿瘤与良性肿瘤的数量:

图 16.4 - 恶性与良性诊断条形图

图 16.4 - 恶性与良性诊断条形图

现在我们有了这些信息和条形图,我们可以开始使用数据集中的数值来查看其他组合和比较。

  1. 您可以运行不同的分析来查看哪些更相关,但现在,我们只是要使用周长均值和纹理均值来创建我们的散点图。以下代码片段显示了如何使用seaborn库来创建这些:
y_target = dataset['diagnosis']
dataset.columns.values
dataset['target'] = dataset['diagnosis'].map({0:'B',1:'M'})
#Create scatterplot of mean perimeter and mean texture
sns.scatterplot(x = 'perimeter_mean', y = 'texture_mean', data = dataset, hue = 'diagnosis', palette = 'bright');

创建了我们的散点图后,算法将返回以下输出,显示了平均周长散点图与平均纹理散点图的比较:

图 16.5 - 平均周长与平均纹理散点图

图 16.5 - 平均周长与平均纹理散点图

我们将在这里暂停数据分析。但是,请注意,您可以将此示例推进得更远。事实上,您可以在 Kaggle 中找到对这个特定数据集进行的多个应用和分析,以及一些开发人员和编码人员如何整合机器学习以进行预测。生物信息学的世界是广阔的,数据科学应用也在不断增长。在这些问题中使用 Python 是有帮助的,因为它易于使用并且有适用的库。

问题 3 - 使用 Python 分析特定人群的数据

对于本节,我们将以这种方式陈述我们的问题 - 现在是 2020 年,世界被一种名为SARS-COV-19的病毒,也称为冠状病毒COVID-19,的大流行所压倒。数据是广泛可用的,我们试图看看特定位置发生了什么情况,特别是该位置的死亡人数是如何增长的。我们找到了纽约时报的 GitHub 存储库,其中包含 COVID-19 数据,并下载了每日更新的主数据。让我们看看我们需要做什么以及我们如何找到它。

定义要分析和识别人群的具体问题

这个问题很广泛。太广泛了!因此,让我们首先看一个地点,仅限一个月。例如,让我们选择波多黎各和十月份。从主.csv文件中,我们只提取了波多黎各特定的数据,并将其添加到我们的存储库中。同样,可以在纽约时报的 covid-19-data 存储库中找到主数据,并且您可以使用完整数据、特定州甚至特定县进行多种分析。

现在,我们将集中于创建一个关于 2020 年 10 月波多黎各特定死亡数据的可视化。仅仅通过查看数据,我们就可以看到死亡人数正在上升。请看下面的截图:

图 16.6 - 2020 年 10 月波多黎各前 20 天的数据

图 16.6 - 2020 年 10 月波多黎各前 20 天的数据

从前面的截图可以看出,死亡列继续上升,病例数量也是如此,我们稍后将在本问题中再次查看。

虽然以表格格式读取的数据可能有所帮助,但如果我们要呈现这些信息,特别是如果我们想要识别趋势并影响政策变化,那么可视化表示就至关重要。因此,让我们看看如何为这些特定数据创建散点图。完整文件可以在ch16_CovidPR.py中找到:

  1. 与数据一起工作时,我们需要确保导入我们将使用的库:
import pandas as pd
import matplotlib.pyplot as plt
  1. 接下来,我们需要获取我们的文件。请记住,有多种方法可以做到这一点。您可以给 Python 提供文件的完整位置,或者您可以首先识别目录,然后只提供文件名。在运行程序之前,请确保更改您将使用的.csv文件的位置:
df = pd.read_csv('C:\\...\\us-PuertoRico.csv')
  1. 在确定文件后,我们将只需使用日期创建一个简单的散点图作为我们的x轴,以及死亡人数作为我们的y轴。此代码片段中的下几个命令是为了使图表更易于阅读,例如y轴标签,x-tick标记的旋转以及图表的标题。x-tick标记是水平轴或x轴的分割标记。您可以在图 16.6中看到x-tick标记和标签:
plt.scatter(df['date'], df['deaths'])
plt.xticks(rotation = 90)
plt.title('Deaths per day during October 2020 due to COVID19 in Puerto Rico')
plt.ylabel('Number of Deaths')
fig.tight_layout()
plt.show()
plt.savefig('COVID_PR.png')

从前面的代码片段可以看出,我们还创建了一个图像文件,以备将来使用。该图表将显示在我们的屏幕上,如下截图所示:

图 16.7 - 2020 年 10 月波多黎各每日死亡人数由于 COVID-19

图 16.7 - 2020 年 10 月波多黎各每日死亡人数由于 COVID-19

这是一个有用的图表,可以看出死亡人数正在稳步增加。我们还可以做更多的事情,比如尝试确定回归,这是我们可以使用 Python 的numpy库做的另一个功能,欢迎您去尝试!

现在,我们将看一下按日期分类的病例。代码与以前相同,只是我们的y轴和标题将不同。完整代码可以在ch16_CovidPR_2文件中找到。由于代码非常相似,我们在这里不分享它。但是,我们的结果图可以在下面的截图中看到:

图 16.8 - 2020 年 10 月波多黎各 COVID-19 每日病例

图 16.8 - 2020 年 10 月波多黎各 COVID-19 每日病例

从前面的截图中可以看出,波多黎各的病例数量每天都在上升。我们可以对这两个图表做很多事情;分析它们的回归,通过查看其他月度数据来验证额外的趋势等等。您已经看到如何根据您的.csv文件创建一个简单的图表来显示数据;其余的就取决于您了。我们将在下一节中研究一个新问题。

问题 4 - 使用 Python 创建房屋数据模型

让我们来看一个问题,我们想要显示布鲁克林,纽约的房地产市场的趋势和信息。数据集包括 2003 年至 2017 年纽约市房屋销售数据的信息。使用的数据集已经以可用格式合并,并且可以在 Kaggle 上找到(www.kaggle.com/tianhwu/brooklynhomes2003to2017)。此外,.csv文件的副本可以在 GitHub 存储库中找到,文件名为brooklyn_sales_map.csv

定义问题

对于这个特定的问题,我们有一个庞大的数据文件。我们可以按社区查看信息,按年份比较销售价格,将建造年份与社区进行比较以找到趋势、历史等等。我们可以花费数小时、数天、数周来处理这个数据集。所以让我们尝试把精力集中在这个例子中要完成的事情上。为此,我们将创建两个可视化模型。第一个是根据销售年份的房屋百分比的水平条形图。第二个是显示房屋销售地点的价格范围的条形图。

水平条形图可以帮助更清晰地显示数据,以便我们可以看到房屋价格范围以及是否有显著变化。垂直条形图可以按社区显示相同的价格范围,因此我们可以看到根据房屋销售地点的不同是否有显著变化。

算法和数据的可视化表示

让我们看一下代码片段。完整的文件可以在 GitHub 存储库的ch16_housingBrooklyn.py下找到。和往常一样,在尝试运行程序之前不要忘记更新文件位置:

  1. 对于这个特定的程序,我们需要pandasmatplotlib库,所以我们需要导入它们:
import pandas as pd
import matplotlib.pyplot as plt
  1. 接下来,我们需要读取我们的文件。这就是你需要更新这段代码以便在你的机器上运行它的地方:
df = pd.read_csv('C:\\...\\brooklyn_sales_map.csv')
  1. 现在我们要创建我们的bins。这些是我们的值范围,当我们创建图表时会用到它们,你可以在以下几行代码中的df['price_range']下看到它们:
bins = [-100000000,20000,40000,60000,80000,100000,1000000,10000000,500000000]
ranges_prices = ['$0-$200k','$200k-$400k','$400k-$600k','$600k-$800k','$800k-$1mlln','$1mlln-$10mlln','$10mlln-$100mlln','$100mlln-$500mlln']
df['price_range'] = pd.cut(df['sale_price'], bins = bins, labels = ranges_prices)
  1. 现在我们要定义一个函数,我们将在其中转换一些数据。请注意,我们在数据集的每一年上运行该函数,以找到我们稍后将用于housing_df的总百分比:
def convert(year):
    return df[df['year_of_sale'] == year].groupby('price_range').size()
percent_total = [x/sum(x)*100 for x in [convert(2003),convert(2004),convert(2005),convert(2006),convert(2007),convert(2008),convert(2009),convert(2010),convert(2011),convert(2012),convert(2013),convert(2014),convert(2015),convert(2016),convert(2017)]]
year_names = list(range(2003,2018))
housing_df = pd.DataFrame(percent_total, index = year_names)
ax_two = housing_df.plot(kind = 'barh', stacked = True, width = 0.9, cmap = 'Spectral')
plt.legend(bbox_to_anchor = (1.45, 1), loc='upper right')
ax_two.set_xlabel('Percentages', fontname='Arial', fontsize = 12)
ax_two.set_ylabel('Years', fontname='Arial', fontsize = 12)
ax_two.set_title('Housing Sale ')

前面的片段帮助我们创建了两个模型中的第一个。这是一个水平条形图。我们标记了所有的轴和图表,然后在前面的代码片段中显示的下一行中,我们还定义了我们将在这个图表中使用的颜色映射,这里是'Spectral'。你可以尝试不同的颜色映射以便更容易阅读。看一下我们的第一个图表,如下所示:

图 16.9-布鲁克林每年的房屋销售

图 16.9-布鲁克林每年的房屋销售

请注意我们在前面的截图中使用了百分比。这使我们能够显示每个价格范围的销售额占比,但它并没有显示每个价格范围的实际销售数量。这两件事情是完全不同的。在这里,我们正在寻找趋势。销售额高于 100 万美元的百分比在 2008 年到 2009 年略微下降后一直在增加。在 2017 年,高于这个价格点的销售额比 2003 年要高得多。

但这是总销售额。如果我们只看这张图而不看数字,我们就不知道例如 2017 年总共卖出了更少的房子。再次,需要注意的重点是,这张图对于理解每个价格范围内房屋销售份额非常有帮助,但这就是这张图给我们的全部信息。现在让我们看看文件中剩下的代码。

  1. 在下面的片段中,我们创建了我们的第二张图,它使用我们的信息为每个社区的每个价格范围生成了一个垂直条形图的百分比:
df.groupby(['neighborhood','price_range']).size().unstack().plot.bar(stacked = True, cmap = 'rainbow')
plt.legend(bbox_to_anchor = (1.45, 1), loc = 'upper right')
plt.title('Pricing by Neighborhoods in Brooklyn from 2003 to 2017')
plt.ylabel('Price Range')
plt.xticks(fontsize = 6)

这张图用条形图显示了每个社区的价格范围。让我们看一下以下截图中的第二张图:

图 16.10-布鲁克林 2003-2017 年各社区的定价

图 16.10-布鲁克林 2003-2017 年各社区的定价

正如你从前面的截图中所看到的,我们得到了一些比图 16.1中提供的更详细或更详细的重要信息。在这种情况下,数据是由邻里提供的,并且该数据是按那些邻里的价格范围进行了分类。

当我们查看大型数据集时,我们可以创建多个不同的模型,甚至使用它们来预测未来的值。查看.csv文件中可用的数据,并尝试使用其他数据创建一些不同的表示,例如商业与住宅销售、税务分类等。

问题 5 – 使用 Python 创建电场线图

在本节中,让我们看一下 Python 在工程和特别是物理学中的一些应用。我们将创建一个电场线图为什么我们要创建这种类型的图,并且它到底是什么? 当存在电荷时,就会产生电场。我们使用矢量来显示空间中每一点的电场。在物理学中,电场是单位电荷的电力。看一下正点电荷和负点电荷的电场是什么样子,如下图所示:

图 16.11 – 电场示例

图 16.11 – 电场示例

正如你所看到的,电场线将从电荷开始或结束于电荷。如果它从电荷开始,它是正的,而如果它结束于电荷,它是负的,正如前面的截图所示。较小的电荷会有较少的线条,而较大的电荷会有更多的线条。此外,对于较大的电荷,线条会比较接近,而对于较小的电荷,线条会比较远。

对于我们的问题,我们想要为任意数量的电荷创建一个电场线图。让我们看看下面的代码文件中会是什么样子。请注意,我们已经将代码分解以解释各个部分,但完整的文件可以在 GitHub 存储库中找到:

  1. 像往常一样,首先,我们将导入必要的库:

ch16_electricFieldLines.py

import numpy as np
import matplotlib.pyplot as plt
import random
  1. 然后,我们将通过定义一些变量来设置我们的xy轴:
np.seterr(divide='ignore', invalid='ignore')
#Define the size of the electric field lines grid
N = 20
M = 25
#Set the x and y coordinates
x_coor = np.arange(0, M, 1)
y_coor = np.arange(0, N, 1)
x_coor, y_coor = np.meshgrid(x_coor, y_coor)
E_x = np.zeros((N, M))
E_y = np.zeros((N, M))

在前面的代码片段中,我们致力于定义我们的网格并设置坐标。然后我们创建了网格。meshgrid()函数从向量坐标返回坐标矩阵。

  1. 在设置了我们的坐标并设置了我们的网格之后,我们可以开始定义我们的电荷发生了什么。首先,我们需要确定将绘制多少个电荷:
#Set the number of total charges to plot
nq = 8
#Create empty lists to store coordinates of charges
qq = [[], []]  
for dummy in range(nq): 
    q = random.choice([-1, 1])
    q_x, q_y = random.randrange(1, N), random.randrange(1, M)
    qq[0].append(q_y)
    qq[1].append(q_x)
    for i in range(N):
        for j in range(M):
            denom = ((i - q_x) ** 2 + (j - q_y) ** 2) ** 1.5
            if denom != 0: 
                E_x[i, j] += q * (j - q_y) / denom
                E_y[i, j] += q * (i - q_x) / denom

正如你从前面的代码片段中所看到的,我们确定了电荷的数量后,创建了两个空列表。然后我们使用嵌套的for循环根据电荷的数量向这些列表添加坐标。

  1. 在我们进行必要的数学运算以获得我们的坐标和向量之后,我们可以继续绘制我们的电场线图。我们将使用quiver图,这是一个我们可以用来创建矢量场的matplotlib图形:
C = np.hypot(E_x, E_y)
E = (E_x ** 2 + E_y ** 2) ** .5
E_x = E_x / E
E_y = E_y / E
plt.figure(figsize=(12, 8))
#Plot charges
plt.plot(*qq, 'ms')
#Create 2D array
rr = plt.quiver(x_coor, y_coor, E_x, E_y, C, pivot='middle')
cbar = plt.colorbar()
cbar.ax.set_ylabel('Magnitude')
#Label graph
plt.title('Electric Field Lines in Python')
plt.axis('equal')
plt.axis('off')
plt.show()

重要的是要始终为我们的图表和绘图添加标签,因为这将使信息更易读,特别是对于那些不知道这背后的代码意味着什么或图表和绘图代表什么的人。当我们运行我们的代码片段时,我们会得到以下输出:

图 16.12 – 八个电荷的电场线

图 16.12 – 八个电荷的电场线

正如你在我们的图中所看到的,有正电荷和负电荷。看一下图中右下角的电荷。该电荷是负的,因为箭头指向电荷。最左边的电荷,已经放大,如下所示,显示为正电荷,因为箭头指向远离电荷的方向:

图 16.13 – 放大的正电荷

图 16.13 – 放大的正电荷

  1. 让我们来看最后一个图形,如下截图所示,其中有一个具有三个电荷的电场线图:

图 16.14 - 具有三个电荷的电场线

图 16.14 - 具有三个电荷的电场线

正如您所看到的,这个特定的图包含两个正电荷和一个负电荷。如果我们再次运行,我们可能会得到三个正电荷,例如,因为每次运行算法时,我们都会得到一个新的表示,其中正电荷和负电荷的值是随机的。

这种类型的领域以及学习如何使用矢量和箭头图可以帮助我们表示大量信息。在电场线的情况下,我们可以通过简单的视觉图了解电荷的方向和强度。

看看 GitHub 存储库中的代码片段,并尝试更改一些参数,例如图的大小和电荷的数量。通过这些图的实践和改变一些参数,您可以更容易地创建它们。

问题 6 - 使用 Python 分析基因数据

让我们把注意力转移到查看一个更大的数据集。您正在处理实验室小鼠的数据,并获得了三体小鼠和这些小鼠的蛋白质表达的数据。由于数据量巨大,我们从 Kaggle 的公共领域文件中截取了一些数据。我们只关注这些小鼠的六种蛋白质表达,并且再次只关注研究中的三体(唐氏综合征)小鼠。完整的文件可以在 Kaggle 上找到www.kaggle.com/ruslankl/mice-protein-expression。截断文件可以在我们的 GitHub 存储库中找到。

假设您不知道从这些数据的哪里开始。您甚至应该看什么?嗯,这通常是数据科学中遇到的第一件事。我们并不总是能参与研究设计或数据收集。很多时候,我们会收到大量的数据文件,并需要找出要查找的内容,如何解决问题,无论我们决定的问题是什么,以及如何以最佳方式显示信息。

另外,这是提醒您在尝试运行此程序之前更改文件位置的提示。这个非常简单的程序可以在ch16_pairplots.py文件中找到。让我们从算法开始:

  1. seaborn库可以帮助我们一点,让我们开始。我们可以创建pairplot(),它将使用直方图和散点图相关联.csv文件中的数值数据。这有点像一个神奇的魔术。我们可以用两行代码来看看我们看到了什么。看看生成图 16.7所需的两行代码(请注意,实际上有四行代码,但我没有计算我用来导入库的两行代码):
import seaborn as sns
import pandas as pd
df = pd.read_csv('C:\\...\\Data_Cortex_Nuclear.csv')
sns.pairplot(df, hue = 'Treatment')

当您运行此程序时,请耐心等待。算法可能很简单,但它在后台所做的事情并不简单。请看以下截图以查看我们的配对图:

图 16.15 - 三体小鼠中蛋白质表达的配对图与治疗变量

图 16.15 - 三体小鼠中蛋白质表达的配对图与治疗变量

请注意,我们的数据根据治疗方式分为两种颜色,分别是注射甲胺醇或盐水。从图中我们可以看到,一些蛋白质似乎比其他蛋白质具有更高的相关性。让我们暂停一下。

  1. 现在假设我们的目标不是根据治疗来检查表达,而是根据类别。然后我们可以运行代码,但首先我们将在算法中将色调更改为class。结果如下所示:

图 16.16 - 三体小鼠中蛋白质表达的配对图与类变量

图 16.16 - 三体小鼠中蛋白质表达的配对图与类变量

请注意,这些图表非常相似。然而,这些图表之间的区别在于根据另一个特征来确定每个点的位置。例如,在class变量图表中,我们有四种颜色,因为在我们的特定数据集中有四类老鼠。

它们分别是t-CS-s,指的是被刺激学习(电击)并注射生理盐水的老鼠;t-CS-m,指的是被刺激学习(电击)并注射美金刚烷的老鼠;t-SC-s,指的是未被刺激学习并注射生理盐水的老鼠;以及t-SC-m,指的是未被刺激学习并注射美金刚烷的老鼠。

通过查看我们的相关性,我们可以看到许多蛋白质之间存在强烈的正相关性,比如NR2A_NBDNF_N。无论这是否相关,无论在我们的研究中是否重要,无论是否显著,这些都是我们在进行研究时需要考虑的事情。一旦我们看到图表,我们可以选择进一步探索信息。

在查看这个数据集时,另一种有用的图表类型是箱线图。我们可以使用箱线图来查看我们想更仔细观察的蛋白质的类别的蛋白质表达水平。让我们以NR2A_N蛋白为例。使用seaborn箱线图,我们可以使用ch16_boxplot.py文件中的代码为这种特定蛋白创建一个图表。像往常一样,首先检查文件位置:

import pandas as pd
import scipy.stats as stats
import matplotlib.pyplot as plt
import seaborn as sns
df = pd.read_csv('C:\\...\\Data_Cortex_Nuclear.csv')
protein = df[['NR2A_N', 'class']].dropna()
sns.boxplot(x='class', y='NR2A_N', data = protein)
plt.show

在上面的代码中,我们确定了我们想要比较的事物,即蛋白质和类别。然后我们将使用我们的seaborn库创建箱线图,如下所示:

图 16.17 - 按类别绘制 NR2A_N 蛋白表达的箱线图

图 16.17 - 按类别绘制 NR2A_N 蛋白表达的箱线图

从图表中可以看出,我们的三体老鼠的分布因类别而异,未被刺激学习并注射生理盐水的老鼠在这种蛋白质的表达上显示出更广泛的范围。让我们尝试将这种蛋白质更改为数据集中的其他蛋白质之一,即ITSN1_N蛋白。以下截图显示了生成的箱线图:

图 16.18 - 按类别绘制 ITSN1_N 蛋白表达的箱线图

图 16.18 - 按类别绘制 ITSN1_N 蛋白表达的箱线图

在这个特定的箱线图中,我们可以确定t-CS-mt-SC-m类别中的异常值,即注射了美金刚烷的两类老鼠。这可能告诉我们需要进一步了解美金刚烷注射与该特定蛋白之间的任何关系。如果我们要观察非三体老鼠,如果其他因素相同,这些数据(范围)是否适用于该蛋白? 这些是我们在观察这类数据集时会问自己的一些问题。

如果你记得的话,计算思维过程很少是一条直线。如果我们确定了我们想要在算法中考虑的事情,我们不会只是让我们的算法独自运行并决定它已经完成了,所以我们不会对它进行更改。我们会回去重新确定我们需要什么,对我们的设计进行必要的更改,然后再次创建我们的算法。这更接近于当我们处理更大的数据集时会发生的情况。我们会查看一些初始的可视化,也许创建一些不同类型的图表,运行一些统计分析,然后决定下一步该如何处理数据。这只是 Python 可能实现的一小部分。

问题 7 - 使用 Python 分析股票

是时候玩一些股票了。您可以通过Quandl访问大量数据,该平台允许免费使用 API 进行教育用途。也有一些高级数据集可用。我们将坚持教育目的,所以这对我们的要求应该足够了。

在这个问题中,我们将学习如何从 Quandl 获取数据并查看 VZ 股票价格。VZVerizon股票价格的代码。我们将使用它们来使用quandl来预测价格,这是 Python 的一个包,也是一个充满有用信息的网站。让我们来看一下我们如何获取我们想要的信息。完整的代码,除了 API 密钥,可以在我们的存储库中的ch16_stockAnalysis.py文件中找到:

  1. 让我们来看一下如何导入数据。你需要自己的 API。如果你想检查另一个股票,比如AMZN,你可以用'WIKI/AMZ'替换'EOD/VZ'AMZNAmazon股票的代码。但让我们来看看我们的VZ集:
import quandl
import numpy as np
from sklearn.linear_model import LinearRegression
from sklearn.svm import SVR
from sklearn.model_selection import train_test_split
#Get data from Quandl. Note that you'll need your own API to substitute in the api.key below.
quandl.ApiConfig.api_key = '…'
VZ = quandl.get('EOD/VZ')
print(VZ.head())

当我们运行上述代码时,我们得到了数据集中前五个值的表格。以下截图显示了我们的数值表:

图 16.19 - EOD/VZ 股票表

图 16.19 - EOD/VZ 股票表

  1. 现在假设我们只想关注调整后的收盘价,以便以后进行预测。那么我们可以使用以下代码片段来实现:
#Grab the Adj_Close column
VZ = VZ[['Adj_Close']]
print(VZ.head())

运行上述代码后,我们的调整后的表格如下所示:

图 16.20 - EOD/VZ 调整后的股票收盘价表

图 16.20 - EOD/VZ 调整后的股票收盘价表

现在我们已经学会了获取当前数据,我们将现在使用我们放在 GitHub 存储库中的数据集来进行操作,以确保你能够复制结果。然后你可以尝试使用 Quandl API 来获取当前数据。

让我们来看一下文件名为VZ.csv的数据集。这包含了 1983 年至 2020 年 4 月的 VZ 相同数据。我们从这个数据集中想要什么?我们想要做一些预测。所以让我们建立那个模型。

请注意,代码相当长,所以包含了你需要的所有内容的文件(除了第 15 行的文件位置,你需要添加),在ch16_predictionsModel.py中。但让我们来看一下那个文件中的一些代码片段:

  1. 以下代码片段将为数据集创建一个图。它从文件中选择Date列并将其设置为索引值。然后它创建一个图并为图和坐标轴添加标签:
VZ["Date"]=pd.to_datetime(VZ.Date,format="%Y-%m-%d")
VZ.index=VZ['Date']
plt.figure(figsize=(16,8))
plt.plot(VZ["Close"],label='Close price history')
plt.title('Closing price over time', fontsize = 20)
plt.xlabel('Time', fontsize = 15)
plt.ylabel('Closing price', fontsize = 15)
plt.show()

我们还没有看到模型。我们还没有定义我们的训练数据。我们只是看了我们的股票价格从1983年到2020年的情况,如下所示。请注意,第一个刻度标签显示1984。我们的数据似乎是在1984年之前开始的。刻度标记是自 1980 年以来每 4 年一次,如下所示:

图 16.21 - VZ 股票随时间的收盘价

图 16.21 - VZ 股票随时间的收盘价

从前面的图表中可以看出,股票价格在任何情况下都不是线性的。它们上升、下降,然后再上升。一个预测模型将需要大量数据,以便我们能够准备出最佳的预测。我们的数据集有 9166 行数据。这将在下一步中派上用场。

  1. 让我们来看一下我们将要使用的另一段代码:
VZ3=VZ2.values
train_data=VZ3[0:750,:]
valid_data=VZ3[750:,:]
VZ2.index=VZ2.Date
VZ2.drop("Date",axis=1,inplace=True)
scaler=MinMaxScaler(feature_range=(0,1))
scaled_data=scaler.fit_transform(VZ2)
x_train_data,y_train_data=[],[]
for i in range(60,len(train_data)):
    x_train_data.append(scaled_data[i-60:i,0])
    y_train_data.append(scaled_data[i,0])

x_train_data,y_train_data=np.array(x_train_data),np.array(y_train_data)
x_train_data=np.reshape(x_train_data,(x_train_data.shape[0],x_train_data.shape[1],1))

请注意代码中train_data=VZ3[0:750,:]行中的750值。这意味着我只使用了数据的前 750 行,而不是可能的 9166 行数据来训练我的模型。这不太好。

让我们来看一下以下截图,显示了我们运行此预测模型时的结果。请注意,我们选择将原始信息复制到我们的图形中。Python 会指出这可能是我们想要修复的问题。这取决于你是否要这样做。目前,将原始数据作为我们图形的叠加提供了一个很好的视觉比较模型的方式:

图 16.22 - 收盘价预测

图 16.22 - 收盘价预测

正如您在前面的图表中所看到的,这里显示的橙色部分是原始的、复制的值。绿色显示了我们模型的预测。它们并不糟糕,但也不如它们本应该的那样紧密。

  1. 让我们看看当我们使用 7500 行数据时会发生什么,这大约是可用数据的 82%。需要注意的是,GitHub 存储库中的文件使用了 7500 的值,但您可以随意更改和调整这些值,以便测试模型的准确性。以下图表显示了我们的结果:

图 16.23 - 使用 7500 行数据的预测模型

图 16.23 - 使用 7500 行数据的预测模型

请注意,在这个模型中,真实线条和预测线条之间的距离更近了。这是因为我们用来训练模型的数据越多,我们的预测就会变得越好。

在我们离开这个例子之前,请注意我们并没有在这里涵盖代码文件的全部内容。本书的其他部分已经讨论了一些代码,因此我们专注于算法的新部分和对算法解决方案至关重要的部分。该代码文件的最后一部分确实使用了长短期记忆(LSTM)模型。LSTM 模型是一种人工循环神经网络。我们在机器学习中使用这个模型来创建深度学习模型。

我们的模型实际上能预测股票的价格吗?不行。否则,我们在市场上就会轻松很多。但模型确实可以非常准确地预测价格是否会上涨或下跌。

问题 8 - 使用 Python 创建卷积神经网络(CNN)

在这一部分,我们将研究一个使用人工智能AI)的问题。更具体地说,我们将致力于创建一个卷积神经网络,或者CNN那么 CNN 是什么?CNN 是一种深度学习算法。CNN 将图像作为输入。然后根据预定条件对图像进行处理和赋予重要性,这将帮助我们区分和分类图像。

以下图表说明了卷积神经网络所涉及的过程:

图 16.24 - 卷积神经网络过程

图 16.24 - 卷积神经网络过程

卷积神经网络(CNN)是为了简化我们对图像进行分类的方式而创建的,同时又不牺牲我们希望从图像分析中得到的准确性。这就好像我们在应用滤镜一样。一旦我们应用了滤镜,我们就可以看到特征。前面的图表显示了这个过程的简化示意图。

我们将要深入研究的问题是手写训练和分析。在考虑计算思维过程时,我们真正想要做的是尽可能准确地分析手写。为此,我们分析数百甚至数千张图像来创建和训练我们的模型。我们使用的图像越多,我们的模型就会越准确。

对于我们的模型,我们将使用一个包含 70,000 张图像的数据集。前 60,000 张图像用于训练,而我们将使用其他 10,000 张进行测试。完整的代码可以在ch16_CNN_mnist.py文件中找到。我们将看一些来自该代码的片段,并进行一些调整以展示额外的组件。只要您安装了所有必要的库和软件包,就可以在 GitHub 存储库中运行文件而不进行更改。

让我们开始设计模型:

  1. 让我们首先看一段代码片段,它将上传数据集,然后显示数据集中的第一项:
from keras.datasets import mnist
#Grab the testing and training data from existing dataset
(X_train, y_train), (X_test, y_test) = mnist.load_data()
import matplotlib.pyplot as plt
#Take a look at the first item in the dataset
plt.imshow(X_train[0], cmap='Greys')

我们在训练集中使用索引0来查看第一幅图像。cmap属性将使色图变为灰色。您可以根据需要进行调整。顺便说一句,对于那些有困难看到颜色或有特殊颜色需求的人来说,改变色图可以显著改变图像的感知方式。让我们看一下以下的第一幅图像:

图 16.25 - MNIST 训练集中的第一幅图像

图 16.25 - MNIST 训练集中的第一幅图像

从前面的截图中可以看出,这是一个手写样本,很可能是数字5。我们可以多次运行程序,使用不同的索引来查看数据集中的其他样本。以下截图显示了其中一些样本及其相应的索引:

图 16.26 - 数据集中的样本图像按索引

图 16.26 - 数据集中的样本图像按索引

我们使用的数据不是定量的,而是定性的。我们正在查看图像,因此我们需要一个可以分析这些图像的过程。为此,我们使用One-Hot 编码,它用新的二进制变量替换整数编码变量。

  1. 现在我们已经看了我们正在使用的东西,让我们使用以下代码片段来重塑和编码我们的模型。作为提醒,完整的代码可以在存储库中找到,但是一些组件会略有不同(例如我们的文件不会测试数据集中的图像):
#Reshape the model 
X_train = X_train.reshape(60000,28,28,1)
X_test = X_test.reshape(10000,28,28,1)
from keras.utils import to_categorical
#Use One-Hot encoding
y_train = to_categorical(y_train)
y_test = to_categorical(y_test)
y_train[0]

如您所见,我们正在将图像分成训练集和测试集。然后对它们进行编码。

  1. 一旦我们进行了一次性编码,我们就可以创建我们的模型:
#Creating the model
from keras.models import Sequential
from keras.layers import Dense, Conv2D, Flatten
model = Sequential()
model.add(Conv2D(64, kernel_size=3, activation='relu', input_shape=(28,28,1)))
model.add(Conv2D(32, kernel_size=3, activation='relu'))
model.add(Flatten())
model.add(Dense(10, activation='softmax'))
#Compile the model
model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])

在前面的代码片段中,我们使用了softmax函数。softmax函数有时被称为归一化指数函数。我们用它来规范化输出。

  1. 现在让我们训练模型。我们将首先拟合模型,然后验证数据。看一下这段代码:
#train the model
model.fit(X_train, y_train, validation_data=(X_test, y_test), epochs=3)

这就是训练和测试的好处之一。也就是说,当我们理解并练习它们时,我们意识到只需要几行代码就可以做一些非常了不起的事情。前面的两行代码(第三行是注释)将产生一些很棒的事情,并允许我们的算法测试其他图像。

  1. 现在我们可以预测数据集中的图像。我们将从最后四个开始,因为每个人都从相同的四个数字开始,所以这次我想从后面开始。请注意,要有耐心。在这个算法中有成千上万的图像需要处理。当时代运行时,您将看到一个时钟,它会告诉您信息处理需要多长时间。对于这个特定的算法,只需要几分钟。让我们看一下我们需要运行预测的代码片段:
#Predict last 4 images
model.predict(X_test[9996:])

当我们运行这段代码时,我们得到了一系列相当复杂的数字。看一下以下的截图。我们已经突出显示了讨论的代码的关键部分:

图 16.27 - CNN 图像的模型预测

图 16.27 - CNN 图像的模型预测

所以,我可以告诉你,第一个预测的数字是 3。我们怎么知道这个数字代表 3 呢?因为每个列表代表数字 0 到 9。所以想象一下,用[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]来替换第一个列表。因此,如果我们把这个看作索引,以01结尾的数字(在前面的截图中突出显示)是索引 3,也就是数字 3。所以我们的数字是 3、4、5 和 6。

  1. 但你相信这个模型吗?我们可以直接返回到这次讨论开始时的代码片段,并打印我们的结果。记得稍微修改代码以打印测试图像,而不是训练图像,就像以下的代码片段中所示:
plt.imshow(X_test[9996], cmap='Greys')

在运行代码时,请记住你需要为每个索引运行代码以查看图像。以下截图显示了测试图像的每个相关索引的图像:

图 16.28 – 测试数据验证图像

图 16.28 – 测试数据验证图像

正如你所看到的,我们的模型预测了这些索引中每个手写数字图像的正确值。

在结束讨论之前,重要的是要注意,这些模型目前在网站上被广泛使用,用于验证访问网站的访客是人类还是机器人。一些网站会有CAPTCHA,有时会提供手写字母,用户必须识别才能继续。这些 CAPTCHA 通常也使用深度学习。CNN 和这些模型的应用是无穷无尽的。

总结

在本章中,我们能够更深入地探讨计算思维的更多主题,特别是在处理数据和深度学习方面,使用 Python 编程语言。我们学会了如何创建 pairplots 来确定数据集中变量之间的关系。我们还学会了如何生成各种类型的图表来直观地表示我们的数据集。我们还学会了如何使用 Python 创建电场线。简而言之,我们应用了我们在之前章节中学到的知识,并在解决实际问题时扩展了我们的知识。

这本书的真正目的是:展示 Python 应用的广泛性,同时关注上下文中的真实问题。我们是否涵盖了 Python 的所有功能?这几乎是不可能的,因为 Python 的能力不断增长,这是因为它易于使用,易于学习,并且由于其开源性质,不断增加了许多应用程序。希望你能够使用一些新的脚本,了解一些你尚未探索的功能和能力,并享受探索这些场景。

我们是否会有能力说我们已经创造了完美的算法?我们这本书的作者认为不会。原因是我们总是在思考如何改进。我们总是质疑额外的应用。我们总是想要使它们更加高效。这正是计算思维帮助我们做的。我们可以分析、设计、测试、回顾,看我们是否达到了想要的目标,然后进行改进、重新设计、进行测试,然后重复。

希望在本章之后,你有机会练习并了解更多关于 Python 的能力。希望在阅读本书之后,你有机会了解编程中计算思维的重要性。感谢你加入我们的旅程!

posted @ 2024-05-04 21:30  绝不原创的飞龙  阅读(28)  评论(0编辑  收藏  举报