理解求和问题
将数值求和是编程中相当常见的问题。例如,假设您有一个数字列表 [1, 2, 3, 4, 5],并且想要将它们加在一起来计算它们的总和。使用标准算术,您将执行以下操作:
1 + 2 + 3 + 4 + 5 = 15
就数学而言,这个表达式非常简单。它会引导您完成一系列简短的加法,直到找到所有数字的总和。
可以手动进行这种特定的计算,但想象一下其他一些情况可能不太可能。如果您的数字列表特别长,则手动添加可能效率低下且容易出错。如果您甚至不知道列表中有多少项,会发生什么?最后,想象一个场景,您需要添加的项目数量会动态或不可预测地发生变化。
在这样的情况下,无论你有一个长的还是短的清单数字,Python对于解决问题非常有用求和问题.
如果您想通过从头开始创建自己的解决方案来对数字进行求和,那么您可以尝试使用for循环:
>>>>>> numbers = [1, 2, 3, 4, 5]
>>> total = 0
>>> for number in numbers:
... total += number
...
>>> total
15
在这里,您首先创建total
并将其初始化为0
。这多变的作为累加器您可以在其中存储中间结果,直到获得最终结果。循环遍历numbers
和更新total
通过使用累加每个连续值增强作业.
您还可以将for
循环在一个功能。这样,您可以为不同的列表重用代码:
>>>>>> def sum_numbers(numbers):
... total = 0
... for number in numbers:
... total += number
... return total
...
>>> sum_numbers([1, 2, 3, 4, 5])
15
>>> sum_numbers([])
0
In sum_numbers()
,你取一个可迭代的——具体来说,一个数值列表——作为参数并且返回输入列表中值的总和。如果输入列表为空,则函数返回0
。这for
循环与您之前看到的循环相同。
您还可以使用递归而不是迭代。递归是一个函数式编程在函数自己的定义内调用函数的技术。换句话说,递归函数在循环中调用自身:
>>>>>> def sum_numbers(numbers):
... if len(numbers) == 0:
... return 0
... return numbers[0] + sum_numbers(numbers[1:])
...
>>> sum_numbers([1, 2, 3, 4, 5])
15
当您定义递归函数时,您将面临陷入无限循环的风险。为了防止这种情况,您需要定义两个基本情况停止递归和递归情况调用该函数并启动隐式循环。
在上面的例子中,基本情况意味着零长度列表的总和是0
。递归情况意味着总和是第一个值,numbers[0]
,加上其余值的总和,numbers[1:]
。由于递归情况在每次迭代中使用较短的序列,因此您预计会在以下情况下遇到基本情况:numbers
是一个零长度列表。作为最终结果,您将获得输入列表中所有项目的总和,numbers
.
笔记:在此示例中,如果您不检查空输入列表(您的基本情况),则sum_numbers()
永远不会陷入无限递归循环。当你的numbers
列表的长度达到0
,代码尝试访问空列表中的项目,这会引发IndexError
并打破循环。
通过这种实现,您永远不会从此函数中得到总和。你会得到一个IndexError
每次。
在 Python 中对数字列表求和的另一个选项是使用减少()从功能工具。要获得数字列表的总和,您可以传递运算符.add或适当的拉姆达函数作为第一个参数reduce()
:
>>>>>> from functools import reduce
>>> from operator import add
>>> reduce(add, [1, 2, 3, 4, 5])
15
>>> reduce(add, [])
Traceback (most recent call last):
...
TypeError: reduce() of empty sequence with no initial value
>>> reduce(lambda x, y: x + y, [1, 2, 3, 4, 5])
15
您可以致电reduce()
减少,或折叠式的, function
连同一个iterable
作为参数。然后reduce()
使用输入函数来处理iterable
并返回单个累积值。
在第一个例子中,归约函数是add()
,它需要两个数字并将它们加在一起。最终结果是输入的数字之和iterable
。作为一个缺点,reduce()
提出一个类型错误当你用空来调用它时iterable
.
在第二个例子中,归约函数是lambda
返回两个数字相加的函数。
由于像这样的求和在编程中很常见,因此每次需要对一些数字求和时编写一个新函数是大量重复性工作。此外,使用reduce()
不是您可用的最具可读性的解决方案。
Python提供了专门的内置函数来解决这个问题。该函数调用方便和()。由于它是一个内置函数,因此您可以直接在代码中使用它,而无需输入任何事物。
Python 入门sum()
可读性是背后最重要的原则之一Python 的哲学。想象一下在对值列表求和时您要求循环执行的操作。您希望它循环一些数字,将它们累积在中间变量中,然后返回最终的总和。但是,您可能可以想象一个不需要循环的更易读的求和版本。您希望 Python 获取一些数字并将它们相加。
现在想想如何reduce()
做求和。使用reduce()
可以说,与基于循环的解决方案相比,它的可读性和直接性都较差。
这就是为什么Python 2.3添加sum()
作为内置函数,为求和问题提供 Pythonic 解决方案。亚历克斯·马泰利贡献了该函数,该函数现在是对值列表求和的首选语法:
>>>>>> sum([1, 2, 3, 4, 5])
15
>>> sum([])
0
哇!这很整洁,不是吗?它读起来像简单的英语,并清楚地传达您在输入列表上执行的操作。使用sum()
比a更具可读性for
循环或一个reduce()
称呼。不像reduce()
, sum()
不提出TypeError
当你提供一个空的可迭代对象时。相反,它可以理解地返回0
.
您可以致电sum()
有以下两个参数:
-
iterable
是一个必需的参数,可以保存任何 Python 可迭代对象。可迭代对象通常包含数值,但也可以包含列表或元组.
-
start
是一个可选参数,可以保存初始值。然后将该值添加到最终结果中。它默认为0
.
在内部,sum()
添加start
加上中的值iterable
从左到右。输入中的值iterable
通常是数字,但您也可以使用列表和元组。可选参数start
可以接受数字、列表或元组,具体取决于传递给的内容iterable
。它不能需要一个细绳.
在以下两节中,您将学习使用的基础知识sum()
在你的代码中。
所需参数:iterable
接受任何 Python 可迭代对象作为其第一个参数使得sum()
通用的、可重复使用的、多态性。由于此功能,您可以使用sum()
与列表、元组、套, 范围对象,以及字典:
>>>>>> # Use a list
>>> sum([1, 2, 3, 4, 5])
15
>>> # Use a tuple
>>> sum((1, 2, 3, 4, 5))
15
>>> # Use a set
>>> sum({1, 2, 3, 4, 5})
15
>>> # Use a range
>>> sum(range(1, 6))
15
>>> # Use a dictionary
>>> sum({1: "one", 2: "two", 3: "three"})
6
>>> sum({1: "one", 2: "two", 3: "three"}.keys())
6
在所有这些例子中,sum()
计算输入可迭代中所有值的算术和,无论其类型如何。在两个字典示例中,都调用sum()
返回输入字典的键的总和。第一个示例默认对键求和,第二个示例对键求和,因为.keys()调用输入词典。
如果您的字典在其值中存储数字,并且您想对这些值而不是键求和,那么您可以使用.values()就像在.keys()
例子。
您还可以使用sum()
与一个列表理解作为一个论点。下面是计算一系列值的平方和的示例:
>>>>>> sum([x ** 2 for x in range(1, 6)])
55
Python 2.4添加生成器表达式到语言。再次,sum()
当您使用生成器表达式作为参数时,可以按预期工作:
>>>>>> sum(x ** 2 for x in range(1, 6))
55
此示例展示了解决求和问题的最 Pythonic 技术之一。它通过一行代码提供了一个优雅、可读且高效的解决方案。
可选参数:start
第二个可选参数,start
,允许您提供一个值来初始化求和过程。当您需要按顺序处理累积值时,此参数非常方便:
>>>>>> sum([1, 2, 3, 4, 5], 100) # Positional argument
115
>>> sum([1, 2, 3, 4, 5], start=100) # Keyword argument
115
在这里,您提供一个初始值100
到start
。最终效果是sum()
将此值添加到输入可迭代中的值的累积和中。请注意,您可以提供start
作为一个位置论证或作为关键字参数。后一个选项更加明确和可读。
如果您不提供价值start
,那么它默认为0
。默认值为0
确保返回输入值总和的预期行为。
对数值求和
主要目的sum()
是提供一种 Pythonic 方式将数值相加。到目前为止,您已经了解了如何使用该函数对整数求和。此外,您可以使用sum()
与任何其他数字 Python 类型,例如漂浮, 复杂的, 小数.小数, 和分数.分数.
以下是一些使用示例sum()
具有不同数字类型的值:
>>>>>> from decimal import Decimal
>>> from fractions import Fraction
>>> # Sum floating-point numbers
>>> sum([10.2, 12.5, 11.8])
34.5
>>> sum([10.2, 12.5, 11.8, float("inf")])
inf
>>> sum([10.2, 12.5, 11.8, float("nan")])
nan
>>> # Sum complex numbers
>>> sum([3 + 2j, 5 + 6j])
(8+8j)
>>> # Sum Decimal numbers
>>> sum([Decimal("10.2"), Decimal("12.5"), Decimal("11.8")])
Decimal('34.5')
>>> # Sum Fraction numbers
>>> sum([Fraction(51, 5), Fraction(25, 2), Fraction(59, 5)])
Fraction(69, 2)
在这里,您首先使用sum()
和浮点数字。使用特殊符号时值得注意函数的行为inf
和nan
在通话中float("inf")
和float("nan")
。第一个符号代表一个无穷值,所以sum()
回报inf
。第二个符号代表NaN(不是数字)价值观。由于你不能将数字与非数字相加,所以你得到nan
因此。
其他示例对以下迭代求和complex
, Decimal
, 和Fraction
数字。在所有情况下,sum()
使用适当的数字类型返回结果累积和。
连接序列
虽然sum()
主要用于对数值进行操作,您还可以使用该函数来连接列表和元组等序列。为此,您需要提供适当的值start
:
>>>>>> num_lists = [[1, 2, 3], [4, 5, 6]]
>>> sum(num_lists, start=[])
[1, 2, 3, 4, 5, 6]
>>> # Equivalent concatenation
>>> [1, 2, 3] + [4, 5, 6]
[1, 2, 3, 4, 5, 6]
>>> num_tuples = ((1, 2, 3), (4, 5, 6))
>>> sum(num_tuples, start=())
(1, 2, 3, 4, 5, 6)
>>> # Equivalent concatenation
>>> (1, 2, 3) + (4, 5, 6)
(1, 2, 3, 4, 5, 6)
在这些示例中,您使用sum()
连接列表和元组。这是一个有趣的功能,您可以使用它来展平列表列表或元组元组。这些示例起作用的关键要求是选择适当的值start
。例如,如果你想连接列表,那么start
需要持有一份清单。
在上面的例子中,sum()
在内部执行串联操作,因此它仅适用于支持串联的序列类型,字符串除外:
>>>>>> num_strs = ["123", "456"]
>>> sum(num_strs, "0")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: sum() can't sum strings [use ''.join(seq) instead]
当你尝试使用sum()
到连接字符串,你得到一个TypeError
。正如异常消息所示,您应该使用str.join()在 Python 中连接字符串。稍后当您到达以下部分时,您将看到使用此方法的示例使用替代方案sum()
.
使用 Python 进行练习sum()
到目前为止,您已经学习了使用的基础知识sum()
。您已经学习了如何使用此函数将数值相加以及连接列表和元组等序列。
在本节中,您将看到更多有关何时以及如何使用的示例sum()
在你的代码中。通过这些实际示例,您将了解到,当您执行需要查找一系列数字之和作为中间步骤的计算时,此内置函数非常方便。
您还将了解到sum()
当您使用列表和元组时会很有帮助。您将看到的一个特殊示例是当您需要展平列表列表时。
计算累计和
您将编写的第一个示例与如何利用start
用于对数值的累积列表求和的参数。
假设您正在开发一个系统来管理给定产品在多个不同销售点的销售。每天,您都会从每个销售点收到一份已售数量报告。您需要系统地计算累计总和,以了解整个公司在一周内销售了多少件产品。为了解决这个问题,你可以使用sum()
:
>>>>>> cumulative_sales = 0
>>> monday = [50, 27, 42]
>>> cumulative_sales = sum(monday, start=cumulative_sales)
>>> cumulative_sales
119
>>> tuesday = [12, 32, 15]
>>> cumulative_sales = sum(tuesday, start=cumulative_sales)
>>> cumulative_sales
178
>>> wednesday = [20, 24, 42]
>>> cumulative_sales = sum(wednesday, start=cumulative_sales)
>>> cumulative_sales
264
...
通过使用start
,您可以设置一个初始值来初始化总和,这允许您将连续的单位添加到先前计算的小计中。在本周末,您将获得公司的已售单位总数。
计算样本的平均值
另一个实际用例sum()
就是在进行进一步计算之前将其用作中间计算。例如,假设您需要计算算术平均值数值样本。算术平均数,也称为平均的,是值的总和除以值的数量,或者数据点,在样本中。
如果你有样本 [2, 3, 4, 2, 3, 6, 4, 2] 并且你想手动计算算术平均值,那么你可以解决这个操作:
(2 + 3 + 4 + 2 + 3 + 6 + 4 + 2) / 8 = 3.25
如果您想使用 Python 来加快速度,可以将其分为两部分。该计算的第一部分是将数字相加,这是一个任务sum()
。运算的下一部分除以 8,使用样本中的数字计数。来计算你的除数, 您可以使用长度():
>>>>>> data_points = [2, 3, 4, 2, 3, 6, 4, 2]
>>> sum(data_points) / len(data_points)
3.25
在这里,调用sum()
计算样本中数据点的总和。接下来,您使用len()
获取数据点的数量。最后,执行所需的除法来计算样本的算术平均值。
在实践中,您可能希望将此代码转换为具有一些附加功能的函数,例如描述性名称和空样本检查:
>>>>>> # Python >= 3.8
>>> def average(data_points):
... if (num_points := len(data_points)) == 0:
... raise ValueError("average requires at least one data point")
... return sum(data_points) / num_points
...
>>> average([2, 3, 4, 2, 3, 6, 4, 2])
3.25
>>> average([])
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in average
ValueError: average requires at least one data point
里面average()
,您首先检查输入样本是否有任何数据点。如果没有,那么你提出一个ValueError
带有描述性消息。在此示例中,您使用海象操作员将数据点的数量存储在变量中num_points
这样你就不需要打电话len()
再次。这返回声明计算样本的算术平均值并将其发送回调用代码。
笔记:计算数据样本的平均值是统计和数据分析中的常见操作。 Python 标准库提供了一个方便的模块,称为统计数据来进行此类计算。
在里面statistics
模块,你会发现一个名为意思是():
>>>>>> from statistics import mean
>>> mean([2, 3, 4, 2, 3, 6, 4, 2])
3.25
>>> mean([])
Traceback (most recent call last):
...
statistics.StatisticsError: mean requires at least one data point
这statistics.mean()
函数的行为与average()
您之前编写的函数。你打电话时mean()
通过数值样本,您将获得输入数据的算术平均值。当您将空列表传递给mean()
,你会得到一个statistics.StatisticsError
.
请注意,当您拨打电话时average()
通过适当的样本,您将获得所需的平均值。如果你打电话average()
使用空样本,然后你会得到ValueError
正如预期的那样。
求两个序列的点积
您可以使用解决的另一个问题sum()
正在寻找点积两个等长的数值序列。点积是以下各项的代数和产品输入序列中的每对值。例如,如果您有序列 (1, 2, 3) 和 (4, 5, 6),那么您可以使用加法和乘法手动计算它们的点积:
1 × 4 + 2 × 5 + 3 × 6 = 32
要从输入序列中提取连续的值对,您可以使用压缩()。然后,您可以使用生成器表达式来将每对值相乘。最后,sum()
可以对乘积求和:
>>>>>> x_vector = (1, 2, 3)
>>> y_vector = (4, 5, 6)
>>> sum(x * y for x, y in zip(x_vector, y_vector))
32
和zip()
,您可以使用每个输入序列的值生成一个元组列表。生成器表达式在每个元组上循环,同时将先前排列的连续值对相乘zip()
。最后一步是使用以下方法将产品添加在一起sum()
.
上面示例中的代码有效。但是,点积是为相等长度的序列定义的,那么如果提供不同长度的序列会发生什么?在这种情况下,zip()
忽略最长序列中的额外值,这会导致错误的结果。
为了处理这种可能性,您可以将调用包装到sum()
在自定义函数中并提供对输入序列长度的正确检查:
>>>>>> def dot_product(x_vector, y_vector):
... if len(x_vector) != len(y_vector):
... raise ValueError("Vectors must have equal sizes")
... return sum(x * y for x, y in zip(x_vector, y_vector))
...
>>> dot_product((1, 2, 3), (4, 5, 6))
32
>>> dot_product((1, 2, 3, 4), (5, 6, 3))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in dot_product
ValueError: Vectors must have equal sizes
这里,dot_product()
接受两个序列作为参数并返回它们相应的点积。如果输入序列具有不同的长度,则该函数会引发ValueError
.
将功能嵌入自定义函数中可以让您重用代码。它还使您有机会以描述性方式命名该函数,以便用户只需阅读其名称即可知道该函数的用途。
展平列表列表
展平列表列表是 Python 中的一项常见任务。假设您有一个列表列表,需要将其展平为一个包含原始嵌套列表中所有项目的列表。在决定如何做时,您有多种选择扁平化列表在Python中。例如,您可以使用for
循环,如以下代码所示:
>>>>>> def flatten_list(a_list):
... flat = []
... for sublist in a_list:
... flat += sublist
... return flat
...
>>> matrix = [
... [1, 2, 3],
... [4, 5, 6],
... [7, 8, 9],
... ]
>>> flatten_list(matrix)
[1, 2, 3, 4, 5, 6, 7, 8, 9]
里面flatten_list()
,循环迭代包含在的所有嵌套列表a_list
。然后将它们连接起来flat
使用增强赋值操作(+=
)。结果,您将获得一个平面列表,其中包含原始嵌套列表中的所有项目。
但坚持住!您已经学会了如何使用sum()
连接本教程中的序列。您可以使用该功能来展平列表列表吗?就像在上面的示例中所做的那样?是的!就是这样:
>>>>>> matrix = [
... [1, 2, 3],
... [4, 5, 6],
... [7, 8, 9],
... ]
>>> sum(matrix, [])
[1, 2, 3, 4, 5, 6, 7, 8, 9]
那很快!一行代码和matrix
现在是一个平面列表。然而,使用sum()
似乎不是最快的解决方案。
使用列表推导式是在 Python 中展平列表列表的另一种常见方法:
>>>>>> def flatten_list(a_list):
... return [item for sublist in a_list for item in sublist]
...
>>> matrix = [
... [1, 2, 3],
... [4, 5, 6],
... [7, 8, 9],
... ]
>>> flatten_list(matrix)
[1, 2, 3, 4, 5, 6, 7, 8, 9]
这个新版本的flatten_list()
使用理解而不是常规for
环形。但是,那嵌套推导式阅读和理解可能具有挑战性。
使用。附加()是展平列表列表的另一种方法:
>>>>>> def flatten_list(a_list):
... flat = []
... for sublist in a_list:
... for item in sublist:
... flat.append(item)
... return flat
...
>>> matrix = [
... [1, 2, 3],
... [4, 5, 6],
... [7, 8, 9],
... ]
>>> flatten_list(matrix)
[1, 2, 3, 4, 5, 6, 7, 8, 9]
在这个版本中flatten_list()
,阅读你的代码的人可以看到该函数迭代了每个sublist
在a_list
。在第二for
循环,该函数迭代每个item
在sublist
最终填充新的flat
列出与.append()
。可以说,可读性是该解决方案的一个优势。
使用替代方案sum()
正如您已经了解到的,sum()
一般而言,对于处理数值很有帮助。然而,当涉及到浮点数时,Python 提供了一种替代工具。在数学,你会发现一个名为fsum()这可以帮助您提高浮点计算的总体精度。
您可能有一项任务想要连接或链接多个可迭代对象,以便可以将它们作为一个整体使用。对于这种情况,您可以查看迭代工具模块功能链().
您可能还有一个想要连接字符串列表的任务。您在本教程中了解到无法使用sum()
用于连接字符串。这个函数并不是为字符串连接而构建的。最Pythonic的替代方法是使用str.join().
浮点数求和:math.fsum()
如果您的代码不断地对浮点数求和sum()
,那么你应该考虑使用math.fsum()
反而。该函数执行浮点计算比sum()
,这提高了计算的精度。
根据其文档, fsum()
“通过跟踪多个中间部分和来避免精度损失。”该文档提供了以下示例:
>>>>>> from math import fsum
>>> sum([.1, .1, .1, .1, .1, .1, .1, .1, .1, .1])
0.9999999999999999
>>> fsum([.1, .1, .1, .1, .1, .1, .1, .1, .1, .1])
1.0
和fsum()
,您会得到更精确的结果。但是,您应该注意的是fsum()
并没有解决表示浮点运算错误。以下示例揭示了此限制:
>>>>>> from math import fsum
>>> sum([0.1, 0.2])
0.30000000000000004
>>> fsum([0.1, 0.2])
0.30000000000000004
在这些示例中,两个函数返回相同的结果。这是因为无法准确表示这两个值0.1
和0.2
以二进制浮点数表示:
>>>>>> f"{0.1:.28f}"
'0.1000000000000000055511151231'
>>> f"{0.2:.28f}"
'0.2000000000000000111022302463'
不像sum()
, 然而,fsum()
当您将非常大的数字和非常小的数字相加时,可以帮助您减少浮点错误传播:
>>>>>> from math import fsum
>>> sum([1e-16, 1, 1e16])
1e+16
>>> fsum([1e-16, 1, 1e16])
1.0000000000000002e+16
>>> sum([1, 1, 1e100, -1e100] * 10_000)
0.0
>>> fsum([1, 1, 1e100, -1e100] * 10_000)
20000.0
哇!第二个例子非常令人惊讶并且完全失败sum()
。和sum()
, 你得到0.0
因此。这与正确结果相差甚远20000.0
,当你得到fsum()
.
如果您正在寻找一个方便的工具来连接或链接一系列可迭代对象,那么请考虑使用chain()
从itertools
。该函数可以接受多个迭代并构建一个迭代器从第一个、第二个生成项目,依此类推,直到耗尽所有输入可迭代对象:
>>>>>> from itertools import chain
>>> numbers = chain([1, 2, 3], [4, 5, 6], [7, 8, 9])
>>> numbers
<itertools.chain object at 0x7f0d0f160a30>
>>> next(numbers)
1
>>> next(numbers)
2
>>> list(chain([1, 2, 3], [4, 5, 6], [7, 8, 9]))
[1, 2, 3, 4, 5, 6, 7, 8, 9]
你打电话时chain()
,您可以从输入可迭代对象中获取项目的迭代器。在此示例中,您可以从以下位置访问连续的项目numbers
使用下一个()。如果您想使用列表,那么您可以使用list()
使用迭代器并返回常规 Python 列表。
chain()
也是在 Python 中展平列表列表的一个不错的选择:
>>>>>> from itertools import chain
>>> matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
>>> list(chain(*matrix))
[1, 2, 3, 4, 5, 6, 7, 8, 9]
展平列表列表chain()
,你需要使用可迭代拆包运算符 (*
)。该运算符解压所有输入可迭代对象,以便chain()
可以与它们一起工作并生成相应的迭代器。最后一步是调用list()
构建所需的平面列表。
连接字符串str.join()
正如您已经看到的,sum()
不连接或加入字符串。如果您需要这样做,那么 Python 中可用的首选且最快的工具是str.join()
。此方法采用字符串序列作为参数并返回一个新的连接字符串:
>>>>>> greeting = ["Hello,", "welcome to", "Real Python!"]
>>> " ".join(greeting)
'Hello, welcome to Real Python!'
使用.join()
是连接字符串最有效、最 Python 的方法。在这里,您使用字符串列表作为参数,并根据输入构建单个字符串。注意.join()
在连接过程中使用调用方法的字符串作为分隔符。在此示例中,您调用.join()
在由单个空格字符组成的字符串上(" "
),所以原始字符串来自greeting
在最终字符串中用空格分隔。