“真正学懂计算机的人(不只是’编程匠’)都对数学有相当的造诣,既能用科学家的严谨思维来求证,也能用工程师的务实手段来解决问题。
——李开复。
很多程序员会格外重视提升自己的编程思维。从理解问题到找出路径,这一过程会经历分解、模式识别、抽象、算法四大步骤。
但正如李开复教授所言,除了编程思维,数学思维也是程序员手中的利器。
计算机排版系统TeX的发明者唐纳德·克努特(Donald Knuth)和他在斯坦福大学的同事创造了“具体数学”这一名词,来描述克努特在斯坦福大学教授的课程。
克努特发现“他的技能中缺少一些数学工具,他需要一些数学工具,以便对计算机程序有彻底的、充分的理解,这些数学工具与他在大学里作为数学专业学生所学的东西完全不同”。
程序员需要具备抽象思维的能力,而这正是数学教育所着重培养的能力。计算机科学则提供模型和动手实践的联系来帮助清晰地讲解较为抽象的数学。重要的是,这两门学科都会介绍的“精确思考”,是寻求解决问题的实际方案的一种非常重要的方法。《计算思维与Python》选择Python这种流行的编程语言,使得读者更加容易阅读。这本书教你在提升编程思维的同时,优化自己的数学思维。
作者: [美] 玛丽亚·利特文(Maria Litvin)
我们先简单来说说这本书,这本书原版书名为Mathematics for the Digital Age and Programming in Python。
书的内容是沿用了“更早讲Python”的版本,包含介绍Python特性,为读者提供了必要的工具,还让读者可以更快地以更加“Python式(pythonic)”的惯用方式,开始编写Python代码。不同的是结合英文版两个不同版本,在这本书中,还增加了两章(“第9章 海龟绘图”和“第14章 向量和矩阵”),以及介绍斐波那契数列的一节(“第10章 序列与和”的10.5节);更新了许多示例、练习和解答;更改了标题,从而更好地匹配这一系列主题和快速变化的技术环境与词汇。可以说这本书是一本专门为提升两种思维的程序员定制的。
虽然结合了两个英文版本,但这本书的主要理念仍然没有改变:介绍离散数学概念和思维,这些概念是所有有基础的程序员必备的基本知识。这种数学知识很容易学习,但大多数中小学数学课程还没有包括离散数学的内容(高等数学会涉及离散数学)。这本书的数学部分包括许多动手编程练习,这些练习可以强化学生对编程和数学的认识。
“那么,这是一本数学书还是一本计算机编程书?”这可能是心急的读者心中的第一个问题。但为什么必须选择呢?这是图书管理员的困境:“它是属于数学类还是计算机类?”有一个简单的解决方案:在每类书架上各放一本。
最关键的是,这本书会教会大家一种特定的思维方式——精确思维,以及如何解决需要这种思维方式的问题。数学和计算机编程都能培养精确思维的能力,并解决那些需要精确解的问题。
程序员的数学思维
如果我们能够造出“时间机器”,让欧几里得穿越时空来到现代化的世界,他会觉得很欣慰,因为在现代技术的浪潮中,他熟悉的几何学仍然在学校里教授。“老对手”牛顿和莱布尼茨都会感到非常满意,因为成千上万的美国高二和高三学生正在学习如何求导和使用积分。
但是,离现在不远的乔治·布尔 (George Boole),尽管他的名字在每一种现代计算机编程语言中都是不朽的,但他仍然需要搜寻几十本 教材才能找到他提出的代数。至于约翰·冯·诺依曼(John von Neumann),这位才华横溢的数学家,也是计算机技术的先驱之一……好吧,按照他一贯的乐观态度,他会预测在 20 年左右的时间里,每个小学生都会学习与门、或门和非门。但是,为什么事实不是如此呢?
数学教会我们欣赏严谨论证的美。从长远来看,这比解决当前实际问题的课程更有价值。数学并不是存在于真空中的——它的抽象植根于几个世纪以来积累的实践知识。数学教学借鉴了我们周围世界的例子和类比,至少它应该如此。
然而,我们周围的世界变化得越来越快。在过去的五六十年间,我们的世界变得数字化了。这种变化如此深刻,以至于人们有时难以完全理解。我们的中小学数学课程在很大程度上忽略了这种变化,这是否就是人们难以理解这种变化的原因?
离散数学和计算思维
我们在这本书中汇集了一些与数字世界相关的更容易理解的数学主题。其中许多主题,通常在大学新生课程中以“离散数学”之名讲授。离散数学已成为所有基础数学的代名词,但在标准的初中和高中代数、 微积分初步和微积分课程中,这种数学都被忽略了。
因此,如果你对计算机编程感兴趣,这本书一定能让你成为更好的计算机程序员。如果你对数学更感兴趣,你将有充分的机会解决有趣的问题,并在计算机程序中,对其中的一些问题进行建模。你将熟悉通常初中生和高中生不会接触到的有趣的数学;你将学会解决真实问题(即你事先并不知道如何解决的问题),你将感受到数学推理和证明的力量。
Python和离散数学
我们来看看Python 代码实现变量和算术运算符。
编程中“变量”的概念类似于代数中的变量,但它在编程中不那么抽象,更实用。
Python有7个算术运算符:普通的+、−、*和/,以及3个Python或编程特有的运算符。
Python 代码结构
在 Python 中,#符号除了在引号内的情况外,表示“注释”(见下图)。
注释的目的是使代码更具可读性。注释可以记录一段代码的作用,或解释模糊的代码。
解释器并不关心注释,它只是跳过从#到行尾的所有文本。
上图中的 def 和 return 是 Python 的“关键字”,即在编程语言中具有特殊含义的字,不能用于命名程序员自己的变量或函数。
def 表示我们正在定义一个函数,return 指定函数返回给调用者的值。(一旦定义了一个函数,你就可以从其他语句中调用它。
注意“def”行末尾的冒号——它是语法规则所必需的。
Python 3.7 有 35 个关键字,下图列出了其中的一些。
当你在 IDLE 中输入 Python 代码时,不同的语法元素将以不同的颜色显示。注释是红色的,关键字默认为橙色。
Python 区分大小写,除了代表通用常量的 3 个保留字(None、True 和 False),所有保留字必须用小写字母书写。
add_numbers 是我们给函数的名字,n 是我们给它的“参数”的名字(也称为“哑元”,即输入值)。
为变量和函数提供合理的、有意义的名称,这非常重要。
Python 中的名称只能包含字母、数字和下划线字符,名称不能以数字开头。有效的名称如:total、sum_3、_a、n2。
Python 区分大小写,因此 Total 与 total 不同。在我们的示例中,我们可以调用函数 sumFrom1ToN,但更常见的 Python 风格是使用小写单词命名变量和函数,用下划线分隔,如add_numbers 或 sum_digits。
def 行后面的行向右“缩进”。缩进行形成一个相关语句块,在本例中是函数的定义。缩进必须在块内保持一致,这是 Python 语法规则之一。习惯上将下一级别缩进 4 个空格。
一些编程语言使用花括号来描述语句块。Python 不是这样。
在 IDLE 编辑器中按键时,默认情况下会插入 4 个空格,但你可以在 IDLE 的首选项中更改缩进设置。
除了注释之外,通常在 def 行下面包含一个“Docstring”(文档字符串)(见下图)。
这段文本通常放在三引号内(允许注释跨越多行),是可选的。这对于阅读代码的读者很有帮助,并且当你在IDLE 的交互式 Shell 中输入该函数时,它将显示为“提示”。
在 Python 中,每个语句通常都写在单独的一行上。
一个例外是用三引号('''或""")括起来的字符串试试:
>>> msg='''Anditalwaysgoeson andonandon andonandon''' >>> msg
Python 返回了 msg 的值:
'And it always goes on\nand on and on\nand on and on
消息中的\n 表示“换行”。当输出包含\n 的字符串时,\n 将下一个字符移动到下一行的开头。
例如:
>>> print('Anditalwaysgoeson\nandonandon\nandonandon') And it always goes on and on and on and on and on
或:
>>> print('Line1\nLine2') Line 1 Line 2
如果文档字符串超过一行,必须使用"""…"""或'''…''',但 Python 的格式手册建议始终使用三引号,即使对于单行文档字符串——为了保持一致性,使它更容易扩展。
如果一个语句太长而无法放在一行上,则可以在行尾添加\(反斜杠)并继续下一行。
例如:
>>> 1 + 2 + 3 + 4 + 5 + 6 \ + 7 + 8 + 9 + 10 55
但是,最好将该表达式放在括号中。
在多行上编写表达式的首选方式,是将它放在括号中。
例如:
>>> (1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10) 55
Python 允许你在一行上放置多个语句,尽管这不是常见的格式。为此,你必须使用分号分隔语句。
例如:
>>> x=3;y=2;print(x+y) 5
变量
在编程理论中,变量是一个“命名的容器”,可以将之理解为一个附加了名称标签的内存位置。
在不同时间可以将不同的值存储在变量中。你可以检查存储在变量中的值,并将新值放入其中。
例1
>>> x=3 >>> x 3 >>> x=5 >>> x 5 >>> x=2*x-1 >>> x 9
请注意,x = 2*x −1 不是等式,不像在数学中那样,而是一组指令:
取 x 的当前值;
乘以 2;
减去 1;
将结果存回 x 中。
在 Python 中,每个值都是一个“对象”。Python 支持几种内置类型的对象:整数和实数、字符串、列表等。(程序员也可以定义自己的对象类型,并在程序中创建它们的实例。)
Python 有一个名为type 的内置函数,它接收任意对象并返回它的类型。
例2
>>> x = 3 >>> type(x) <class 'int'> >>> x=1.5 >>> type(x) <class 'float'> >>> x='Hello' >>> type(x) <class 'str'> >>> x='*' >>> type(x) <class 'str'>
我们从这些例子中学到了什么?
1.若要引入一个新变量,我们所要做的就是给它命名,并使用=为它赋值。
2.如果我们在提示符处输入变量的名称,Python 将显示它的当前值。
3.Python 有几种内置的对象类型。int 类型的对象包含整数值。float 类型的对象包含实数(或其近似值)。实数的类型称为 float,因为这些数字作为浮点数存储在计算机中。
str 类型的对象包含一串字符(或单个字符)。
4.同一个变量可以在不同的时间保存不同类型的对象。
5.在 Python 中,每个对象“都知道”它自己的类型。我们不必明确告诉变量存储在其中的对象类型。
变量名由程序员选择。
变量的名称只能包含字母、数字和下划线字符,并且不能以数字开头。Python 要求所有变量名称以小写字母或下划线开头。
变量可以用在“表达式”中。
例3
>>> tax_rate=0.05 >>> price=48.00 >>> total=price*(1+tax_rate) >>> total 50.400000000000006
以下语句是如何工作的?
total = price * (1 + tax_rate)
首先,Python 确保定义了变量 price 和 tax_rate,然后获取它们的值,并将它们插入表达式 price* (1 + tax_rate)。
如果其中一个值与使用它的操作不兼容,Python 会“引发异常”(报告错误)。
如果一切正常,Python 会计算结果,并将它放入变量 total 中。
▼
请注意,结果并不准确。误差是由于实数在计算机中近似地表示为二进制浮点数,可能存在小差异。有一种方法可以获得更简洁的输出:
>>> print('Totalsale:{0:6.2f}'.format(total)) Total sale: 50.40
{0:6.2f}是格式字符串中的占位符,它在宽度为 6 的字段中对 float 值进行右对齐,小数点后面有两位数;0:告诉 Python 将第一个参数(total)插入占位符,并适当地对它进行舍入处理。
▲
计算表达式并将它赋值给变量后,即使更改了表达式中使用的变量值,其值也不会自行更改。
例4
# continued from Example 3 ... >>> price=60.00 >>> total 50.400000000000006
要看到 price 的最终值,你需要明确地重新计算该表达式:
>>> total=price*(1+tax_rate) >>> total 63.0
“变量”可以保存一个“常量”,即在程序运行期间不会改变的值。例如:
pi = 3.1415926535897932
▼
说实话,我们没有告诉你关于变量的全部真相。事实上,Python 变量不包含对象——它们包含对象的“引用”。
引用基本上是对象在内存中的地址。某些对象(如长字符串或列表)会占用大量存储空间,将它们的值从一个变量复制到另一个变量会太慢。
作为替代,当我们赋值 b = a 时,我们只将引用(即地址)从 a 复制到 b 中。两者都指向同一个对象(见下图)。
只要对象是“不可变的(immutable)”,即永远不会改变,这不会引起任何混淆。所有数字和字符串都是不可变对象,一旦被创建,它们就无法更改。
但是,列表并非一成不变。
试试这个:
>>> a=[1,2] # list [1, 2] is assigned to variable a >>> b=a >>> b ... >>> a.append(3) >>> b ...
你必须小心!
▲
算术运算符
如你所见,算术运算符+、−、*和/,用于加法、减法、乘法和除法,与算术和代数几乎相同。
与数学不同的是,乘法符号不能省略。
如果没有括号,先执行乘法和除法,然后执行加法和减法。可以像数学中那样使用括号,用于改变操作的顺序。
除了 4 个标准算术运算符+、−、*、/,Python 还有 3 个算术运算符。
n%m(读作“n 模 m”)计算 n 除以 m 时的余数。例如,17%3 得到 2,而 4%10 得到 4。
//运算符称为“整数除法”运算符,通常适用于整数操作数。n//m 的结果是一个最接近其比值的整数,等于或低于其比值。例如,8//3 得到 2,而 5//9 得到 0。
·x**n 表示 x 的 n 次幂。例如,2**3 得到 8。
%和//具有与*和/相同的优先级(等级);**具有更高的优先级,会首先计算。
▼
Python 还有一个内置函数 divmod,当 n 除以 m 时,使用它计算商和余数,如 q, r = divmod(n,m)将 q 设置为 n // m,r 设置为 n%m。
例如:
cents = 144 print(cents, 'cents =', end=' ') quarters, cents = divmod(cents, 25) dimes, cents = divmod(cents, 10) nickels, cents = divmod(cents, 5) print(quarters, 'quarters +', dimes, 'dimes +', nickels, 'nickels +', cents, 'cents')
输出:
144 cents = 5 quarters + 1 dimes + 1 nickels + 4 cents
▲
在前面的例子中,你可能已经注意到了 s += k 这样的语句。+=是“增强(或复合)”赋值语句中使用的符号之一:+=,−=,*=,/=,%=,//=,**=。
x += 3 与 x = x + 3 相同,意味着将 x 加 3。
其他增强赋值以类似的方式工作。你可以将增强赋值与任何“二元运算符(即使用两个操作数的运算符)”一起使用。经验丰富的程序员写出 x = x + 1 而不是 x += 1 这样的语句,会被认为“不酷”。
+=也适用于两个字符串或列表,*=也适用于整数和字符串或列表。
-END-