我为什么会得到一个空字典?
我正在通过一本入门的Python教材学习Python,现在遇到了一个问题:
你需要实现一个叫做index()的函数,这个函数接收两个输入:一个文本文件的名字和一个单词列表。对于列表中的每一个单词,你的函数要找到文本文件中出现这个单词的行,并打印出对应的行号。
举个例子:
>>>> index('raven.txt', ['raven', 'mortal', 'dying', 'ghost', 'ghastly', 'evil', 'demon'])
ghost 9
dying 9
demon 122
evil 99, 106
ghastly 82
mortal 30
raven 44, 53, 55, 64, 78, 97, 104, 111, 118, 120
这是我对这个问题的尝试:
def index(filename, lst):
infile = open(filename, 'r')
lines = infile.readlines()
lst = []
dic = {}
for line in lines:
words = line.split()
lst. append(words)
for i in range(len(lst)):
for j in range(len(lst[i])):
if lst[i][j] in lst:
dic[lst[i][j]] = i
return dic
当我运行这个函数时,得到的是一个空字典。我不明白为什么会得到一个空字典。那么我的函数哪里出了问题呢?谢谢。
3 个回答
首先,你的函数参数用来接收单词的变量叫做 lst
,而你用来存放文件中所有单词的列表也叫 lst
,这样就导致你没有保存传入函数的单词,因为在第4行你又重新定义了这个列表。
其次,你在文件中的每一行上进行循环(第一个 for
),并获取该行中的单词。这样一来,lst
就包含了整个文件中的所有单词。因此,在 for i ...
的循环中,你其实是在遍历从文件中读取的所有单词,没必要再用第三个 for j
来遍历每个单词中的每个字符。
总的来说,在那个 if
语句中,你说的是“如果这个单个字符在单词列表中...”,但实际上并不是这样,所以字典永远不会被填充。
for i in range(len(lst)):
if words[i] in lst:
dic[words[i]] = dic[words[i]] + i # To count repetitions
你需要重新考虑这个问题,即使我的回答也会失败,因为字典中的单词并不存在,会导致错误,但你明白我的意思。祝你好运!
试试这个,
def index(filename, lst):
dic = {w:[] for w in lst}
for n,line in enumerate( open(filename,'r') ):
for word in lst:
if word in line.split(' '):
dic[word].append(n+1)
return dic
这里介绍了一些语言的特性,你需要了解,因为它们会让你以后编程的时候轻松很多。
第一个是字典推导式。简单来说,它用 lst
中的单词作为键,用一个空列表 []
作为每个键的值,来初始化一个字典。
接下来是 enumerate
命令。这个命令让我们可以遍历一个序列中的项目,同时还可以获取这些项目的索引。在这个例子中,因为我们把一个文件对象传给了 enumerate
,所以它会逐行遍历文件。每次循环时,n
是当前行的索引(从0开始),line
是当前行的内容。然后我们再遍历 lst
中的单词。
注意这里我们不需要任何索引。Python 鼓励直接遍历序列中的对象,而不是先遍历索引再根据索引访问对象(比如不推荐使用 for i in range(len(lst)): do something with lst[i]
这种写法)。
最后,in
操作符是一个非常简单的方法,用来检查某个对象是否在某个集合中,语法也很直观。在这个例子中,我们是在问当前的 lst
中的单词是否在当前的 line
中。
注意我们使用 line.split(' ')
来获取当前行中的单词列表。如果不这样做,'the' in 'there was a ghost'
会返回 True
,因为 the
是某个单词的子串。
另一方面,'the' in ['there', 'was', 'a', 'ghost']
会返回 False
。如果条件返回 True
,我们就把它添加到字典中对应键的列表里。
这些内容可能有点多,但理解了这些概念后,解决类似的问题会变得更简单。
你在覆盖lst
的值。你把它当作一个函数的参数使用(这时它是一个字符串列表),又把它当作文件中的单词列表使用(这时它是一个字符串列表的列表)。当你这样做时:
if lst[i][j] in lst
比较总是返回False
,因为lst[i][j]
是一个str
,但lst
只包含字符串的列表,而不是单独的字符串。这意味着对dic
的赋值从未执行,因此你得到的结果是一个空的dict
。
为了避免这种情况,你应该给存储单词的列表使用一个不同的名字,比如:
In [4]: !echo 'a b c\nd e f' > test.txt
In [5]: def index(filename, lst):
...: infile = open(filename, 'r')
...: lines = infile.readlines()
...: words = []
...: dic = {}
...: for line in lines:
...: line_words = line.split()
...: words.append(line_words)
...: for i in range(len(words)):
...: for j in range(len(words[i])):
...: if words[i][j] in lst:
...: dic[words[i][j]] = i
...: return dic
...:
In [6]: index('test.txt', ['a', 'b', 'c'])
Out[6]: {'a': 0, 'c': 0, 'b': 0}
你还有很多地方可以改进。
当你想遍历一个列表时,不需要明确使用索引。如果你需要索引,可以使用enumerate
:
for i, line_words in enumerate(words):
for word in line_words:
if word in lst: dict[word] = i
你也可以直接遍历一个文件(想了解更多信息,可以参考读取和写入文件部分):
# use the with statement to make sure that the file gets closed
with open('test.txt') as infile:
for i, line in enumerate(infile):
print('Line {}: {}'.format(i, line))
实际上,我不明白你为什么要先构建那个words
的列表。你可以直接在遍历文件的同时构建字典:
def index(filename, lst):
with open(filename, 'r') as infile:
dic = {}
for i, line in enumerate(infile):
for word in line.split():
if word in lst:
dic[word] = i
return dic
你的dic
的值应该是列表,因为可能有多行包含相同的单词。现在的情况是你的dic
只会存储最后一行找到的单词:
from collections import defaultdict
def index(filename, words):
# make faster the in check afterwards
words = frozenset(words)
with open(filename) as infile:
dic = defaultdict(list)
for i, line in enumerate(infile):
for word in line.split():
if word in words:
dic[word].append(i)
return dic
如果你不想使用collections.defaultdict
,可以把dic = defaultdict(list)
替换为dic = {}
,然后改成:
dic[word].append(i)
用:
if word in dic:
dic[word] = [i]
else:
dic[word].append(i)
或者,你也可以使用dict.setdefault
:
dic.setdefault(word, []).append(i)
不过这种方法比原来的代码稍慢一些。
注意,所有这些解决方案都有一个特点:如果文件中没有找到某个单词,它将完全不出现在结果中。然而,你可能希望它出现在结果中,值为空列表。在这种情况下,最好在开始循环之前就用空列表初始化dict
,比如:
dic = {word : [] for word in words}
for i, line in enumerate(infile):
for word in line.split():
if word in words:
dic[word].append(i)
你也可以遍历words
而不是行,像这样:
dic = {word : [] for word in words}
for i, line in enumerate(infile):
for word in words:
if word in line.split():
dic[word].append(i)
不过要注意,这样会更慢,因为:
line.split()
返回一个列表,所以word in line.split()
需要扫描整个列表。- 你在重复计算
line.split()
。
你可以尝试通过以下方式解决这两个问题:
dic = {word : [] for word in words}
for i, line in enumerate(infile):
line_words = frozenset(line.split())
for word in words:
if word in line_words:
dic[word].append(i)
注意,这里我们只遍历了一次line.split()
来构建集合,同时也遍历了words
。根据这两个集合的大小,这可能比原来的版本(遍历line.split()
)更慢或更快。
不过在这个时候,交集可能会更快:
dic = {word : [] for word in words}
for i, line in enumerate(infile):
line_words = frozenset(line.split())
for word in words & line_words: # & stands for set intersection
dic[word].append(i)