具有多个迭代器的映射
到目前为止,我们一直向 map 函数传递一个可迭代对象,以及一个需要单个参数的函数,以便将来自该可迭代对象的各个项目传递给后续的函数调用。
我们还可以定义一个带有多个参数的函数,每个参数都可以来自一个单独的可迭代对象。
让我们定义一个函数,它接受两个数字并返回它们的 GCD 或者“最大公约数”。
def gcd(a,b): if a < b: a,b = b,a while(b!=0): a, b = b, a%b return a print(f"gcd of 45 and 30 is {gcd(45,30)}")
我们将定义两个长度相等的独立列表,并将它们与计算 gcd 的方法一起传递给 map
函数。
map 函数将迭代调用gcd(a,b)
方法,其第一个参数将从第一个列表中获取,第二个参数将从第二个列表中获取。
x = [45, 3, 18, 27, 37] y = [30, 5, 12, 81, 9] print(f"x: {x}") print(f"y: {y}") gcds = map(gcd, x, y) # calling map with more than 1 iterables gcds = list(gcds) print(f"GCDs: {gcds}")
请注意,两个列表必须具有相同的长度,因为参数是成对传递给函数的,两个列表各有一个。
如果两个列表的长度不同,则将处理最小可能的对,并丢弃较长列表中的另外元素。
在这种情况下,结果的长度将与较小列表的长度匹配。
请注意,我们传递给 map 的两个(或者更多)可迭代对象不一定必须是同一类型。
这意味着如果一个 iterable 是一个列表,另一个可能是一个元组,第三个可能是一个集合,依此类推。
让我们定义一个函数,它接受一个学生的名字(string
)的 3 个值,他们的卷号。
( int
) 和 cgpa ( float
),并返回一个字典,其中每个项目都用它们的键标记。
我们将把这个函数和 3 个可迭代对象一起传递给 map
函数。
import numpy as np def get_student_dict(name, roll, cgpa): student = { "name": name, "roll no.": roll, "CGPA": cgpa } return student names = ["Adam", "Becka", "Brandon", "Charlotte", "Mariyam"] # a list(length=5) roll_nos = (1, 2, 3, 4, 5) # a tuple(length=5) cgpa = np.array([9.2, 7.6, 8.5, 9.8, 8.7, 4.8]) # a NumPy array(length=6) print(f"names = {names}, type={type(names)}\n") print(f"roll_nos = {roll_nos}, type={type(roll_nos)}\n") print(f"cgpa = {cgpa}, type={type(cgpa)}\n") student_dicts = map(get_student_dict, names, roll_nos, cgpa) print("Student dictionaries:\n") for student in student_dicts: print(f"{student}\n")
这里有几点需要注意:
- 我们传递给 map 的三个可迭代对象各有不同的类型——列表、元组和 NumPy 数组。
- 这些可迭代对象的长度不相等,
cgpa
数组有一个另外的值,该值被map
丢弃。 - 我们不会将返回的映射对象转换为列表或者元组。由于它是可迭代的,我们直接使用
for
循环对其进行迭代。
使用 lambda 中的条件映射
在上一节中,我们讨论了不能在 lambda 函数中使用普通的 Python 语句,我们必须将返回值表示为表达式。
但是,如果我们必须使用 if..else 构造,我们可以使用以下语法将它们包含为表达式的一部分:lambda args: val1 if condition else val2
让我们通过定义一个 lambda 函数来查找给定值是否为偶数来理解这一点。
我们可以使用 map
在数字列表上使用它。
结果将是一个布尔值列表,指示列表中的相应值是否为偶数。
a = [13, 60, 0, 2, 17, 19] print(f"a = {a}\n") is_even = list(map(lambda x: True if x%2==0 else False, a)) print(f"is_even(a) = {is_even}")
用 lambda 映射
到目前为止,我们一直在调用map
函数之前预先定义要传递的函数。
但是 Python 映射函数的真正潜力在它与 lambda
函数一起使用时得以实现。
让我们首先了解什么是“lambda”。
lambda
是一个 Python 关键字,用于创建匿名函数。
匿名函数,顾名思义,就是没有名字的函数。
我们使用 def
关键字定义函数的典型方法包括使用名称声明函数。
我们只需要定义一次这样的函数,并且我们可以在程序的不同位置根据需要多次使用它。
另一方面,匿名函数是在没有名称的情况下构造的,通常不打算在多个位置重复使用。
lambda 函数声明的语法是:lambda arg1, arg2,... : expression
一个 lambda 函数可以接受 1 个以上的参数,但它的返回值必须是一个表达式。
这意味着,在返回值之前,它不能像普通函数那样有多个 Python 语句。
让我们定义一个 lambda 函数来求值的平方。
square = lambda x: x**2 print(f"Square of 12 = {square(12)}")
请注意,lambda 函数没有任何显式的 return 语句,我们指定的“表达式”由函数计算并返回。
另请注意,虽然我们已将 lambda 函数分配给名为“square”的变量,但没有必要这样做,这里只是为了方便起见。
我们可以很好地定义一个 lambda 函数并同时调用它,而无需将其分配给任何变量。
x = (lambda x: x**2)(25) #creating and calling lambda in single step print(f"square of 25 = {x}")
Lambda 函数在我们必须将函数对象作为参数传递给其他函数时特别有用,例如在 map
的情况下。
现在让我们使用 lambda 函数调用 map
来计算列表中所有数字的平方根。
a = [144, 25, 400, 81, 36] print(f"a = {a}") square_roots = map(lambda x: x**(0.5), a) #using lambda to compute square roots square_roots = list(square_roots) print(f"square roots = {square_roots}")
让我们也举一个带有多个参数的 lambda 的例子。
我们将定义一个接受两个参数并返回它们的乘积的 lambda。
然后我们将在带有两个列表的 map
函数中使用它,以找到两个列表中值的成对乘积。
a = [1, 2, 3, 4, 5] b = [10, 20, 30, 40, 50] print(f"a = {a}") print(f"b = {b}") products = list(map(lambda x,y: x*y, a, b)) print(f"products = {products}")
元组上的映射函数
如前所述,可以在任何有效的 Python 可迭代对象上调用 map
方法,例如元组、字符串、字典、NumPy 数组等。
让我们举一个例子来说明它在元组上的用法。
在这里,我们将使用 map
函数将 str.lower
方法应用于以字符串形式存储在元组中的一堆名称。
names = ("John", "Adam", "STANLEY", "toNy", "Alisha") print(f"names: {names}") names_lower = list(map(str.lower, names)) print(f"names in lowercase: {names_lower}")
这里我们没有像之前那样传递用户定义的函数。
我们改为在 Python 中传递 string 模块的内置方法。
请注意,我们必须将函数名称(不带括号)而不是函数调用作为第一个参数传递给 map
。
NumPy 数组上的映射函数
由于 NumPy 数组是可迭代的,我们也可以对它们应用 map 函数。
默认情况下,在多维数组的情况下,map
会将函数应用于沿第一个轴的 NumPy 数组。
让我们首先将 map
应用于一维数组。
我们将使用 map
来查找 NumPy 数组中每个元素的平方。
我们将使用 NumPy 随机种子,因此我们可以生成相同的随机数。
import numpy as np np.random.seed(42) def square(x): return x**2 a = np.random.randint(1,10, size=(6,)) print(f"array a = {a}") a_squared = tuple(map(square, a)) print(f"squares of a = {a_squared}")
映射嵌套列表
到目前为止,调用函数的可迭代对象中的各个值是单个标量值(或者在二维数组输入的情况下是 NumPy 数组)。
我们还可以将 map
函数应用于嵌套列表。
在这里,我们传递给 map
的函数将接受一个列表(或者元组)作为其参数。
让我们考虑一个个人姓名列表。
这些名称不会存储为单个字符串。
相反,它们将被定义为 2 个字符串的列表,其中第一个将存储名字,列表中的第二个元素将是姓氏。
names = [["Stephen", "Hawking"], ["John", "Doe"], ["Christian", "Wolf"], ["Aditi", "Singh"], ["Maria", "Pereira"]] print(f"{'First Name':10} {'Last Name':10}") for name in names: print(f"{name[0]:10} {name[1]:10}")
我们将定义一个函数,该函数接受一个包含名字和姓氏的列表,并返回一个表示个人全名的连接字符串。
我们将使用 map
将此函数应用于我们上面定义的列表中的所有名称。
def get_full_name(name): return " ".join(name) full_names = list(map(get_full_name, names)) print(f"full names: {full_names}")
字典上的映射
我们已经在列表、元组和 NumPy 数组上看到了 map
的用法。
现在让我们了解如何利用该函数来处理字典。
迭代字典不像列表、元组等那样简单,因为字典存储键值对的集合。
如果我们使用 for 循环迭代字典变量,则迭代器变量将在每次迭代期间分配一个字典的键。
让我们通过定义一个字典 electricity_bills
来理解这一点,它的键是电力客户的消费者 ID,值是包含消费者姓名和过去 6 个月电费金额列表的元组。
electricity_bills = { 11001: ("Pete Wolfram",[100, 85, 200, 150, 96, 103]), 11002: ("Jessica Becker", [76, 88, 102, 97, 68, 72]), 11003: ("Alex Jasper",[170, 190, 165, 210, 195, 220]), 11004: ("Irina Ringer",[350, 284, 488, 372, 403, 410]), 11005: ("Sean Adler",[120, 115, 111, 109, 121, 113]) } for k in electricity_bills: print(k)
如果我们直接迭代它,我们只能访问字典的键。map
函数将展示类似的行为。
我们传递给 map
的函数将仅使用字典的键进行迭代调用。
但在这里我们也想处理字典的值。
因此,我们传递给 map
函数的函数应该接收字典中的键和值。
我们可以通过在字典上调用items
方法并使用与map
函数相同的iterable 而不是直接使用字典来实现这一点。
items
方法返回一个 dict_items
对象,该对象将字典的键值对作为列表中的元组。
让我们定义一个函数,它接受这样的键值对,计算客户的平均每月账单,并返回一个包含消费者 ID 和每月平均账单的元组。
然后我们将使用这个函数和 map
来查找字典中所有客户的平均账单。
def calculate_average_bill(consumer): # consumer is a tuple having key-value pair key, value = consumer consumer_id = key bill_amounts = value[1] avg_bill = sum(bill_amounts)/len(bill_amounts) return(consumer_id, round(avg_bill,4)) average_bills = list(map(calculate_average_bill, electricity_bills.items())) print(f"average monthly bills: {average_bills}")
因此,我们得到了一个元组列表,每个元组都有 consumer_id 和平均每月账单。
如果我们只想处理它们的值,我们可以类似地调用字典上的 values()
函数。
在 2D NumPy 数组上映射
现在让我们使用 map
来查找 2D NumPy 数组中每一行的算术平均值。
如果我们迭代多维数组,那么默认情况下,它会在轴 0 上迭代,在二维数组的情况下,它是行轴。
map
函数表现出相同的行为,它将提供的函数迭代地应用于数组中的每一行。
np.random.seed(42) def get_mean(x): m = sum(x)/len(x) return round(m,4) b = np.random.randint(1,100, size=(4,6)) print(f"array b:\n {b}\n") means = list(map(get_mean, b)) print(f"row-wise means: {means}")
我们得到 4 个平均值,数组 b 中的每一行一个。
如果要按列应用函数,可以将数组 b 的转置作为第二个参数传递给 map
。
col_means = list(map(get_mean, b.T)) print(f"column-wise means of b: {col_means}")
或者,我们可以使用 NumPy 的 apply_along_axis
沿 NumPy 数组的任何轴应用函数。
要找到 b 列的均值,我们应该沿轴 0 应用 get_axis
。
col_means = np.apply_along_axis(get_mean, 0, b) print(f"column-wise means using NumPy = {col_means}")
该方法将结果以 NumPy 数组形式返回。
我们是否一直在使用 for
循环对 Python 中的项目列表执行重复性任务?
我们是否希望存在一种更有效的方法来将函数应用于 Python 列表中的每个项目?
如果回答是肯定的,那么我们还没有在 Python 中发现一个重要而强大的工具 map()
函数。
在本教程中,我们将揭示 map 函数的功能,它不仅可以帮助我们实现比 for 循环更高效的迭代,还可以帮助我们编写更干净的代码。
map函数有什么作用?
map
函数帮助我们迭代地将函数应用于 Python 列表中的所有项目,或者任何 Python 可迭代的项目,只需一行代码。
map
函数接受两个参数,第一个参数是应用于可迭代对象(列表、元组、集合等)中的单个项目的函数,第二个参数是可迭代对象本身。
map
函数返回一个映射对象,可以通过调用适当的方法将其转换为所需的可迭代对象(列表、元组、集合等)。
让我们考虑一个 Python 函数,将华氏温度转换为其等效摄氏温度。
我们将把这个函数应用到从不同城市收集的温度列表。
def fahrenheit_to_celcius(F): C = (F-32)*(5/9) return round(C,4) temp_fahrenheit = [100, 95, 98, 105, 110, 32] temp_celcius = [] for tf in temp_fahrenheit: tc = fahrenheit_to_celcius(tf) temp_celcius.append(tc) print(f"Temperatures in Fahrenheit: {temp_fahrenheit}") print(f"Temperatures converted to Celcius: {temp_celcius}")
在这里,我们采用了迭代列表的传统方法,例如:使用 for 循环。
我们首先创建了一个空列表 temp_celcius
,然后在 for 循环中,我们访问列表 temp_fahrenheit
中的每个项目。
我们在这些项目上调用方法 fahrenheit_to_celcius
并将结果添加到 temp_celcius
。
让我们看看如何通过对“map”函数的一次调用来代替这两个步骤。
temp_celcius_map = list(map(fahrenheit_to_celcius, temp_fahrenheit)) print(f"Temperatures converted using map: {temp_celcius}")
请注意我们如何消除空列表的 for 循环和初始化,并将它们替换为对 map
函数的单个调用。
我们使用list
方法将返回的地图对象转换为列表。
如果我们希望我们的结果是一个元组,我们可以等效地使用 tuple()
方法。