命令式编程:可变数据+迭代

我们已经介绍了命令式编程的两个基本概念,接下来将回到推荐程序的例子,以便说明数据可变性和迭代共同作用的强大功能。我们使用的例子是对元素列表进行排序。

5.5.1 为什么要排序?因为运行时间很重要

在深入研究排序细节之前,我们先确定为什么对数据进行排序很有用。原因之一是排序后的数据让数据处理快得多。

但是,如何衡量快与慢?分析程序运行多长时间的关键,是计算在给定大小的输入下它要执行的操作数。计算机科学家很少关心很小的输入的程序速度有多快,如果输入很小,它们几乎总是很快。但是,在处理大量输入时(例如,数百万个用户分别评价了数百甚至数千名艺人),速度就变得至关重要。实际上,如果我们试图构建一个可处理数百万用户的系统,则需要进行更多的优化,并使用完全不同的算法,但这超出了本章的范围。在这里,我们仅向你展示如何加快处理速度,以处理稍大的输入。

为了开始观察运行时间,我们来看一下使用5.3.4节中的函数计算两个列表之间的匹配数需要多长时间,代码如下:

def numMatches(list1, list2):
    ''' return the number of elements that match between 
        list1 and list2 '''

    count = 0
    for item in list1:
        if item in list2: 
            count += 1
    return count

现在,我们暂时假设每个列表中都有4个元素。花一点时间考虑一下你认为程序将进行多少次比较,然后继续阅读下面的内容。

首先,获取第一个列表的第一个元素,然后询问它是否在第二个列表中。in命令有点“欺骗性”,因为它隐藏了大量的比较。Python如何判断元素是否在列表中?它必须将该元素与列表中的每个元素进行比较!因此,在这个例子中,将第一个列表中的第一个元素与第二个列表中的所有4个元素进行比较,以确定它是否在该列表中。

你说:“等等!”如果该元素确实在列表中,则实际上不必检查所有4个元素,而是在找到要找的元素时可以停止检查。这是完全正确的,但实际上在我们的分析中这并不重要。出于类似这样的分析目的,计算机科学家非常悲观。他们很少关心事情进展顺利时会发生什么。他们关心的是在最坏的情况下可能发生的情况。在这种情况下,最坏的情况是该元素不在列表中,而Python必须将它与列表中的每个元素进行比较,以确定它是否不在列表中。由于我们关心的是这种最坏的情况,因此我们进行分析时假设面对最坏的情况。

继续我们的分析,对于列表中的第一个元素,程序对第二个列表中的元素进行了4次比较——隐藏在in命令中的比较。现在,我们的程序移至第一个列表中的第二个元素,在这里它再次与第二个列表进行了4次比较。同样,它对第一个列表中的第三个和第四个元素分别进行了4次比较,总次数为4 + 4 + 4 + 4 = 4×4 = 16。

再说一次,这个数字听起来似乎不太糟糕。毕竟,你的计算机可以在不到1s的时间内完成16次比较。但是,如果我们的名单更长怎么办?如果用户已经评价了100位艺人,而要比较的用户已经评价了1000位(高,但并非不可能)怎么办?这样,系统将不得不用第一个列表中的100个元素的每个元素,与第二个列表中的元素进行1000次比较——总计100×1000 = 105次比较。这仍然不是很大,但是希望你能完全看到事情的发展方向。一般来说,我们上面编写的匹配算法需要进行N×M次比较,其中N是第一个列表的大小,M是第二个列表的大小。简单起见,我们可能就假设两个列表的长度总是相同,即为N,在这种情况下,进行102次比较。

好消息是我们可以做得更好,但是要做到这一点,我们必须对列表本身做一个假设。如果我们的列表是按字母顺序排序的,会怎样呢?这如何让我们的匹配算法更快?答案是,我们可以使列表保持“同步”。也就是说,同时遍历两个列表,而不是从第一个列表中取出一个元素并将它与第二个列表中的所有元素进行比较。例如,如果第一个列表的第一个元素是“Black Eyed Peas”,而第二个列表的第一个元素是“Imagine Dragons”,那么你知道“Black Eyed Peas”没有出现在第二个列表中,因为I已经在B之后了。因此,你可以停止寻找“Black Eyed Peas”,然后转到第一个列表中的下一个元素。

下面是新算法。请记住,它假定列表已排序(稍后我们将讨论如何对列表进行排序)。

1.将计数器初始化为0。

2.将每个列表中的当前元素设置为每个列表中的第一项。

3.重复以下操作,直到到达两个列表之一的末尾。

 3a.比较每个列表中的当前元素。

 3b.如果它们相等,则增加计数器,并将两个列表的当前元素设置为列表中的下一个元素。

 3c.否则,如果第一个列表中的当前元素按字母顺序位于第二个列表中的当前元素之前,就在第一个列表中推进当前元素。

 3d.否则,在第二个列表中推进当前元素。

4.返回计数器的值,该值保存匹配数。

在查看下面的代码之前,问一下自己:“在这里我应该使用哪种循环?是for循环还是while循环?”当你对自己的答案满意时,请继续阅读。

下面是相应的Python代码:

def numMatches( list1, list2 ):
    '''return the number of elements that match between two sorted lists''' 
    matches = 0
    i = 0
    j = 0
    while i < len(list1) and j < len(list2): 
        if list1[i] == list2[j]:
            matches += 1
            i += 1
            j += 1
        elif list1[i] <  list2[j]: 
            i += 1
        else:
            j += 1
    return matches
print numMatches(['a', 'l', 'i', 's', 'o', 'n'], ['a', 'k', 'h', 'i', 'l'])

在字符串上使用运算符==、>和<是完全有效的,并且这些运算符将按字母顺序比较字符串。回顾第4章4.2.3节,文本用数字表示。在这种编码中,所有大写字母都映射到连续的数字,其中“A”取最低的数字,“Z”取最高的数字。小写字母也映射到连续的数字,这些数字都高于“Z”所使用的数字。比较字符串时将使用这些编码。这意味着以大写字母开头的字符串将始终按字母顺序排列,并在以小写字母开头的字符串之前。这一点在我们的程序中很重要,我们将在下面的5.5.2节中讨论。

现在问题仍然存在,这种方法真的比以前比较两个列表中的元素的方法快吗?答案肯定是“是”。让我们再来看一次比较两个列表(每个列表包含4个元素)所需的比较次数。设想没有元素匹配,并且列表按字母顺序准确地交错。也就是说,首先第一个列表中的元素较小,然后第二个列表中的元素较小,依此类推。例如列表["Aretha Franklin", "Coldplay", "Madonna", "Red Hot Chili Peppers"]和["Black Eyed Peas", "Daft Punk", "Maroon 5", "Shakira"]。

对于这两个列表,上面的代码将永远不会在第一个条件上触发——它总是会递增i或j,但不会同时递增。此外,它将查看第一个列表中的元素,恰好在查看第二个列表中的元素之前。本质上,它会查看两个列表中的所有元素。

乍一看,我们似乎没有任何改进。毕竟,我们不是仍然查看了两个列表的所有元素吗?是的,但是现在有一个重要的区别!之前我们针对第一个列表中的每个元素查看第二个列表中的所有元素,而在这里,我们只查看了第二个列表中的所有元素一次。换句话说,每次循环,i或j都会增加,而它们永远不会减少。因此,在查看了该列表的所有元素并查看了另一个列表的元素之后,i或j都将到达列表的末尾。因此,在这个例子中,这意味着我们将恰好进行7次比较。

一般来说,如果列表的长度均为N,则此算法将进行的比较次数为N + N - 1,即2N-1。因此,即使对于第一个列表具有100个元素而第二个列表具有1000个元素的情况,这也只有大约1100次比较,比以前方法的105次有显著改进!

用技术术语来说,计算机科学家将第二种算法称为“线性时间算法”,因为描述比较次数的方程是线性的。第一种算法称为“二次时间算法”,因为其方程是二次的。

5.5.2 一种简单的排序算法:selectionSort

既然我们已经确定了至少一个对数据进行排序的原因(还有很多其他原因,你可能会想到其中的几个),让我们看看一种实际进行排序的算法。

我们考虑一下用户喜欢的艺人或乐队列表,该列表是在程序启动时提示用户输入的。回顾一下,得到该列表的代码如下:

prefs = []
newPref = input("Please enter the name of an artist  
or band that you like: " )

while newPref != '': 
    prefs.append(newPref)
    newPref = input("Please enter an artist or band 
that you like, or just press Enter to see recommendations: ")

print ('Thanks for your input!')

用户输入的艺人或乐队存储在列表prefs中,但该列表完全未排序。我们希望按字母顺序对列表进行排序,并在处理时整理其文本的格式。

首先,为了使我们的生活更轻松并方便用户个人资料之间的匹配过程,我们将遍历列表,让艺人或乐队的名称的格式标准化。具体来说,我们要确保没有前导(或末尾)空格,并且艺人或乐队的名称都用字首大写表示(即每个单词的首字母大写,其余字母均为小写)。即使这种格式在某些艺人或乐队上可能会失败,我们还是会用它,因为这种格式为我们提供了一种标准的表示形式,可以对字符串进行排序和比较,而不必担心会遇到大小写问题。回想一下,所有大写字符串都被认为“小于”小写字符串,并且按字母排序将Zero 7放在will.i.am之前会让用户感到困惑。

由于字符串是不可变的数据类型,因此我们实际上无法更改它们,我们必须用想要的格式生成新字符串。下面是实现这个目标的代码(请注意,我们也构建了一个新列表,这并非绝对必要):

standardPrefs = [] 
for artist in prefs:
    standardPrefs.append(artist.strip().title())

strip函数返回一个与artist相同的新字符串,但没有任何前导或末尾空格。然后title函数以字首大写的形式返回相同的字符串。

既然数据已经标准化,我们可以将它们从最小到最大(按字母顺序排列)进行排序。排序本身就是计算机科学研究的重要课题。

我大概(sort of)知道这很重要!

在这里,我们将讨论一种排序算法,但是关于该主题还有很多要说的。此外,Python包含一个内置的sort函数,该函数使用比我们在这里描述的快得多的算法,对列表中的元素进行排序。因此,实际上,你通常只要使用这个内置函数,如以下示例所示:

>>> list1 = [32, 2, 42, 4, 9, 2, 11]
>>>  list1.sort()
>>> list1
[2, 2, 4, 9, 11, 32, 42]

请注意,sort函数改变了list1的引用,并且不返回任何内容。

为了开发算法,我们从较小的情况开始。从计算上来看,什么是可以重新排列列表的单元?两元素列表是要考虑的最小(非平凡)情况。在这种情况下,最复杂的结果是这两个元素需要彼此交换位置。

实际上,这种交换的思路就是我们所需要的!设想有一个大的列表,我们希望按升序排序。下面是排序过程的算法。

1.首先,我们可以找到最小的元素。然后,我们可以将最小的元素与列表的第一个元素交换。

2.接下来,我们搜索第二个最小的元素,它是列表其余部分中最小的元素。然后,我们可以将它替换为整个列表的第二个元素。

3.我们继续进行交换,以使第三个最小的元素在列表中排在第三位,然后对第四个元素进行相同的操作,依此类推,直到元素被遍历完。

这个算法称为“选择排序”,因为它通过重复选择最小的剩余元素并将它移到列表中的下一个位置来进行排序。

我们需要什么函数来编写选择排序?似乎我们只需要两个函数。

下面是这个算法的Python代码:

def selectionSort(listToSort):
    '''sorts aList iteratively and in-place'''
    for startingIndex in list(range(len(listToSort))): 
        minElemIndex = indexOfMin(listToSort, startingIndex) 
        swap(listToSort, startingIndex, minElemIndex)

# And here is indexOfMin:
def indexOfMin(aList, startIndex):
    '''returns the index of the min element at or after start_index'''

    minElemIndex = startIndex
    for i in range(startIndex, len(aList)): 
        if aList[i] < aList[minElemIndex]:
            minElemIndex = i 
    return minElemIndex


#And swap:
def swap(aList, i, j):
    '''swaps the values of aList[i] and aList[j]'''

    temp = aList[i]       # store the value of aList[i] for a moment 
    aList[i] = aList[j]   # make aList[i] refer to the value of aList[j] 
    aList[j] = temp       # make aList[j] refer to the stored value

请注意,selectionSort不会返回任何内容。但是,当运行它时,我们会看到它有效。调用selectionSort后,列表已完成排序:

>>> preferences
['Maroon 5', 'Adele', 'Lady Gaga']
>>> selectionSort(preferences)
>>> preferences
['Adele', 'Lady Gaga', 'Maroon 5']

5.5.3 为什么selectionSort有效

为什么上面这段代码有效,即使swap和selectionSort没有return语句?有两个关键因素。

首先,列表是可变的。因此,两个(或多个)变量可以引用相同的列表,并且如果你用一个变量对列表元素进行改动,在使用其他变量时也会看到这些改动。但是,指向同一列表的这两个变量在哪里?在上面的示例中,preferences似乎是引用最初的包含3个元素的列表的唯一变量。

下面是第二个因素:当输入传入函数时,函数参数被赋值为每个输入,就像明确编写了赋值语句一样。例如

listToSort = preferences

发生在对selectionSort(preferences)的调用开始时。

因此,只要listToSort和preferences引用相同的列表,对listToSort元素所做的任何更改也会影响preferences的元素。这是因为listToSort的元素和preferences的元素是相同的。

要点:将输入传递给函数等同于将这些输入赋值给该函数的参数。因此,就像在普通赋值中一样,可变和不可变数据类型的微妙区别仍然适用。

图5.4的左侧说明了在第一次调用swap的开始处发生的情况。swap的变量显示为灰色,而selectionSort的变量显示为黑色。请注意,swap的i和j引用列表aList(这只是列表listToSort的另一个名称)中的索引位置。

命令式编程:可变数据+迭代

图5.4 在第一次调用swap的开始和结束处,selectionSort和swap中变量的图形化描述

图5.4的右侧显示了在第一次调用swap的最后变量的样子。swap完成后,它的变量消失了。但是请注意,即使swap的aList、i、j和temp消失了,列表的值也已在内存中更改。由于swap的aList和selectionSort的listToSort以及最初的preferences都是对同一可变元素集合的引用,因此赋值语句对这些元素的影响将在所有这些名称之间共享。毕竟,它们都是相同列表的不同名称!

5.5.4 一种不同排序的swap

考虑对swap和selectionSort函数进行很小的修改,这会对结果产生很大的影响:

def selectionSort(listToSort):
    '''sorts listToSort iteratively and in-place''' 
    for startingIndex in range(len(listToSort)):
        minElemIndex = indexOfMin(listToSort, startingIndex)
        # now swap the elements!
        swap(listToSort[startingIndex], listToSort[minElemIndex])

def swap(a, b):
    '''swaps the values of a and b''' 
    temp = a
    a = b
    b = temp

这段代码看起来与前面的几乎相同,但是现在不起作用:

>>> preferences
['Maroon 5', 'Adele', 'Lady Gaga']
>>> selectionSort(preferences)
>>> preferences
['Maroon 5', 'Adele', 'Lady Gaga']   # Nothing happened!

swap中的变量a和b确实交换了,如图5.5所示。但是aList的元素什么也没有发生,preferences的元素什么也没有发生(这是同一件事)。

命令式编程:可变数据+迭代

图5.5 在第一次调用swap的开始和结束处,selectionSort和新的(错误的)swap中的变量的图形化描述

发生了什么?这次,swap仅有两个参数a和b,它们的引用分别是listToSort [startingIndex]和listToSort[minElemIndex]的引用的副本。请记住,Python的赋值机制:赋值是引用的副本。

因此,当swap这次运行时,其赋值语句在这两个引用的副本上工作。所有交换都按照指定的顺序进行,因此a和b所指的值确实是相反的。但是selectionSort的列表listToSort中保存的引用没有更改。因此,listToSort的值不变。由于preferences的值是listToSort的值,所以什么也不会发生。

正如我们在本节开始时提到的那样,selectionSort只是众多排序算法中的一种,其中一些算法比另一些算法要快(selectionSort并不是特别快,尽管它肯定也不是最慢的算法)。在实践中,尤其是现在,你可能只需要调用Python内置的列表sort函数,它很高效,更重要的是,它的实现正确。例如:

>>> preferences
['Maroon 5', 'Michael Jackson', 'Jennifer Lopez']
>>> preferences.sort()
>>> preferences
['Jennifer Lopez', 'Maroon 5', 'Michael Jackson']

5.5.5 二维数组和嵌套循环

事实表明,你不仅可以在列表中存储数字,还可以在列表中存储任何类型的数据。在第2章中,我们已经看到了许多示例,其中使用列表来存储字符串、数字和这些数据类型的组合。列表的列表不仅是可能的,而且功能强大,也很常见。

我们的律师已建议我们注意,从技术上讲,列表和数组之间存在非常细微的差异,但这时的差异太微不足道,无须担心。

在命令式编程中,列表通常称为“数组”。在本节中,我们研究另一种常见的数组结构:存储其他数组的数组,通常称为“二维数组”。

二维数组背后的概念很简单:它就是一个列表,其元素本身就是列表。例如,代码

>>> a2DList = [[1, 2, 3, 4],[5, 6, 7, 8], [9, 10, 11, 12]]

创建了一个名为a2DList的列表,其中a2DList的每个元素本身就是一个列表。图5.6以图形方式说明了该二维数组(列表),为简明起见,数组名称已缩写为A。在图中,阴影框中的数据存储在CPU中,其余数据(包括对其他列表的引用)存储在内存中。

命令式编程:可变数据+迭代

图5.6 二维数组的图形描述

你可以使用“嵌套索引”访问二维数组中的各个元素,如下所示:

>>> a2DList[0]      # Get the first element of a2DList 
[1, 2, 3, 4]
>>> a2DList[0][0]   # Get the first element of a2DList[0] 
1
>>> a2DList[1][1]   # Get the second element of a2DList[1] 
6

我们还可以询问有关a2DList的高度(行数)和宽度(列数)的问题:

>>> len(a2DList)    # The number of elements (rows) in a2DList 
3
>>> len(a2DList[0]) # The number of elements in a list in
                    # a2DList (i.e., the number of columns)
4

通常,数组具有等长的行,但是“锯齿状数组”(长度不等的数组)肯定是可能的。

事实表明,这种二维结构是表示数据的非常强大的工具。实际上,我们已经在推荐程序中看到了这样的示例:存储的用户的喜好由列表的列表(二维数组)表示。在这个例子中,数组肯定是锯齿状的,因为不同的存储用户给不同数量的艺人或乐队评价。

上面我们写了一些代码来标准化一个特定用户喜好的表示。但是我们可能对这个数据库不太确定——这些列表是否也已标准化(排序)?如果没有,也没问题。编写让所有列表中的所有元素快速标准化的代码很容易:

def standardizeAll(storedPrefs):
    ''' Returns a new list of lists of stored user preferences, 
    with each artist string in Title Case,
    with leading and trailing whitespace removed. 
    '''

    standardStoredPrefs = []
    for storedUser in storedPrefs: 
        standardStoredUser = [] 
        for artist in storedUser:
            standardStoredUser.append(artist.strip().title())
        standardStoredPrefs.append(standardStoredUser)
    return standardStoredPrefs

print(standardizeAll([[' Michael Jackson', 'jennifer lopez'], ['maROON 5']]))

在上面的代码中,外部循环控制各个用户的迭代。也就是说,对于外部循环的每次迭代,变量storedUser被赋值为一个列表,包含一个用户的喜好。然后,内部循环遍历该用户的喜好列表中的元素。换句话说,每个艺人或乐队的名称就是一个字符串。

我们还可以将上面的代码编写如下:

def standardizeAll(storedPrefs):
    ''' Returns a new list of lists of stored user preferences, 
        with each artist string in title case,
        with leading and trailing whitespace removed.
    '''
    standardStoredPrefs = []
    for i in range(len(storedPrefs)): 
        standardStoredUser = []
        for j in range(len(storedPrefs[i])): 
            standardStoredUser.append(storedPrefs[i][j].strip().title())
        standardStoredPrefs.append(standardStoredUser) 
    return standardStoredPrefs

这段代码执行相同的操作,但是我们使用了for循环的索引版本,而不是让for循环直接在列表中的元素上进行迭代。两种结构都可以,但是后一种使得下一个例子更加清楚。

由于列表是可变的,因此实际上我们不必创建一个全新的二维数组,只需更改数组中字符串的格式即可。请记住,字符串本身是不可变的,因此我们无法直接更改列表中存储的字符串。但是,我们可以更改存储在最初列表中的字符串,如下所示:

def standardizeAll(storedPrefs):
    ''' Mutates storedPrefs so that each string is in
        title case, with leading and trailing whitespace removed. 
        Returns  nothing.
    '''
    for i in range(len(storedPrefs):
        for j in range(len(storedPrefs[i])):
            standardArtist = storedPrefs[i][j].strip().title() 
            storedPrefs[i][j] = standardArtist

请注意,这段代码比上面的代码稍微简单一些,并且还避免了创建一个全新的列表的列表的开销。

5.5.6 字典

到目前为止,我们已经研究了一种可变数据:数组(列表)。当然,还有很多其他的可变数据。实际上,在第6章中,你将学习如何创建自己的可变数据类型。现在,我们将研究一种名为“字典”的内置数据类型,它使我们能够在数据之间创建映射。

你是否曾经在字典中查找过dictionary这个词?

为了说明字典的必要性,让我们再次回到推荐程序。到目前为止,在我们的示例中,我们还没有将存储的用户名与他们的喜好相关联。尽管我们不需要存储用户名及其喜好,就可以为每个当前用户计算最佳匹配用户(假设我们知道存储的用户列表不包含当前用户),但这样做有许多优点。举例来说,我们可以确保用户不会与自己相匹配!另外,在系统的扩展版本中,向用户推荐可能的“音乐朋友”也许是不错的选择,用户可能希望记录其爱好。

那么我们如何将用户名与他们的喜好相关联?一种方法是简单地使用列表。例如,我们可能有一个约定,其中每个用户存储的喜好中的第一个元素实际上是该用户的名称。这种方法有效,但它不是很好,并且可能导致错误。例如,如果另一个在该系统上工作的程序员忘记了这种表示形式,而是开始将用户名称看成艺人或乐队名称,该怎么办?尽管不是悲剧,但这仍然是不正确的行为。简而言之,“优雅”的设计很重要!

我们需要一个地方来存储从用户名到用户喜好的映射,我们可以将它传递给所有需要了解这种信息的函数。这是字典派上用场的地方!字典允许你在不可变数据(键)和其他可变或不可变数据(值)之间创建映射。下面是它们如何工作的示例:

>>> myDict = {}     # creates a new empty dictionary

# create an association between 'Ani' and her
# list of preferred artists.
# 'Ani' is the key and the list is the value
>>> myDict['Ani'] = ['Maroon 5', 'Coldplay', 'The Beatles']

# Ditto for Bo and his list
>>> myDict['Bo'] =   ['Jennifer Lopez', 'Michael Jackson',
                       'Nicki Minaj', 'Florida Georgia Line']
>>> myDict                        # display the mappings currently in the dictionary
{'Ani': ['Maroon 5', 'Coldplay', 'The Beatles'],
'Bo': ['Jennifer Lopez', 'Michael Jackson', 'Nicki Minaj', 'Florida Georgia Line']}
>>> myDict['Ani']                 # get the item mapped to  'Ani' 
['Maroon 5', 'Coldplay', 'The Beatles']
>>> myDict['f']                   # get the item mapped to 'f'. Will cause an error
                                  # because we never added 'f' as a  key. 
Traceback  (most  recent  call last):
File "", line 1, in   
myDict['f']

KeyError: 'f'
>>> 'f' in myDict           # Check whether a key is in the  dictionary 
False
>>> list(myDict.keys())     # Get the keys in the dictionary
[Ani', 'Bo']                # keys() actually returns a special kind of type
                            # that you can iterate over, but we've created a
                            # list out of this special type for simplicity

>>> myDict[1] = 'one'       # keys can be any immutable type
>>> myDict[1.5] = [3, 5, 7] # values can also be mutable, and
                            # we can mix types in the same dictionary
>>> myDict[[1, 2]] = 'one'  # Keys cannot be mutable

Traceback (most recent call last):
File "", line 1, in  
myDict[[1, 2]] = 'one'
TypeError: list objects are unhashable

# a shorthand way to create a dictionary
>>> userPrefs = {'Ani': ['Maroon 5', 'Coldplay', 'The Beatles'],
'Bo': ['Jennifer Lopez', 'Michael Jackson', 'Nicki Minaj', 'Florida Georgia Line']}

>>>  userPrefs
{'Ani': ['Maroon 5', 'Coldplay', 'The Beatles'],
'Bo': ['Jennifer Lopez', 'Michael Jackson', 'Nicki Minaj', 'Florida Georgia Line']}

键值对在userPrefs输出中的出现顺序,代表其内部数据表示。它们并不总是以同样的顺序出现。

现在让我们看一下如何修改推荐代码,使用字典而不是列表的列表:

def getBestUser(currUser, prefs, userMap):
    ''' Gets recommendations for currUser based on the users in userMap
        (a dictionary) and the current user's preferences in prefs (a list)
    '''

    bestuser = None 
    bestscore = -1
    for user in userMap.keys():
        score = numMatches(prefs, userMap[user]) 
        if score > bestscore and currUser != user:
            bestscore = score 
            bestuser =  user
    return bestuser

请注意,字典让我们可以确保不会将用户与他们自己存储的喜好匹配!非常简单,也很灵活!

本文摘自《计算机科学概论(Python版)》

命令式编程:可变数据+迭代

全书共7章。第1章介绍计算机科学的概念,引入了用于控制虚拟的“Picobot”机器人的一种简单的编程语言;第2章和第3章介绍Python编程语言,并且结合Python介绍了函数式编程的思想和概念;第4章深入计算机的内部工作原理,从数字逻辑到机器组织,再到用机器语言编程;第5章探讨计算中更复杂的思想,同时探讨诸如引用和可变性等概念,以及包括循环在内的构造、数组和字典;第6章探讨面向对象编程和设计中的一些关键思想;第7章针对问题解决,在计算复杂性和可计算性方面,提供了一些优雅的,但数学上非常合理的处理方法,最终证明了计算机上无法解决的许多计算问题。

本书适合想要通过Python编程来系统学习和了解计算机科学的读者阅读,也可以作为高等院校计算机相关专业的教学参考书。

展开阅读全文

页面更新:2024-05-18

标签:数据   赋值   数组   字符串   变量   算法   字典   喜好   函数   最小   元素   命令   两个   代码   用户   列表   科技

1 2 3 4 5

上滑加载更多 ↓
推荐阅读:
友情链接:
更多:

本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828  

© CopyRight 2020-2024 All Rights Reserved. Powered By 71396.com 闽ICP备11008920号-4
闽公网安备35020302034903号

Top