我为什么会得到一个空字典?

0 投票
3 回答
2360 浏览
提问于 2025-04-18 14:34

我正在通过一本入门的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 个回答

0

首先,你的函数参数用来接收单词的变量叫做 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

你需要重新考虑这个问题,即使我的回答也会失败,因为字典中的单词并不存在,会导致错误,但你明白我的意思。祝你好运!

1

试试这个,

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,我们就把它添加到字典中对应键的列表里。

这些内容可能有点多,但理解了这些概念后,解决类似的问题会变得更简单。

1

你在覆盖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)

撰写回答