在 Python 中构建嵌套字典,从文件中逐行读取



我使用嵌套字典的方式是这样的:

dicty = dict()
tmp = dict()
tmp["a"] = 1
tmp["b"] = 2
dicty["A"] = tmp
dicty == {"A" : {"a" : 1, "b" : 1}}

当我尝试在一个大文件上实现它时,问题就开始了,逐行阅读。这是在列表中打印每行的内容:

['proA', 'macbook', '0.666667']
['proA', 'smart', '0.666667']
['proA', 'ssd', '0.666667']
['FrontPage', 'frontpage', '0.710145']
['FrontPage', 'troubleshooting', '0.971014']

我想得到一个嵌套字典(忽略小数(:

{'FrontPage': {'frontpage': '0.710145', 'troubleshooting': '0.971014'},
 'proA': {'macbook': '0.666667', 'smart': '0.666667', 'ssd': '0.666667'}}

当我逐行阅读时,我必须检查文件中是否仍然找到第一个单词(它们都已分组(,然后再将其作为完整的字典添加到更高的字典中。

这是我的实现:

def doubleDict(filename):
    dicty = dict()
    with open(filename, "r") as f:
        row = 0
        tmp = dict()
        oldword = ""
        for line in f:
            values = line.rstrip().split(" ")
            print(values)
            if oldword == values[0]:
                tmp[values[1]] = values[2]
            else:
                if oldword is not "":
                    dicty[oldword] = tmp
                tmp.clear()
                oldword = values[0]
                tmp[values[1]] = values[2]
            row += 1
            if row % 25 == 0:
                print(dicty)
                break #print(row)
    return(dicty)

我实际上想在熊猫中拥有这个,但现在如果这可以作为字典,我会很高兴。出于某种原因,在阅读了前 5 行之后,我最终得到:

{'proA': {'frontpage': '0.710145', 'troubleshooting': '0.971014'}},

这显然是不正确的。怎么了?

使用 collections.defaultdict() 对象自动实例化嵌套字典:

from collections import defaultdict
def doubleDict(filename):
    dicty = defaultdict(dict)
    with open(filename, "r") as f:
        for i, line in enumerate(f):
            outer, inner, value = line.split()
            dicty[outer][inner] = value
            if i % 25 == 0:
                print(dicty)
                break #print(row)
    return(dicty)

我在这里使用enumerate()生成行数;比保持单独的计数器运行要简单得多。

即使没有defaultdict,你也可以让外部字典保留对嵌套字典的引用,并使用values[0]再次检索它;没有必要保留temp引用:

>>> dicty = {}
>>> dicty['A'] = {}
>>> dicty['A']['a'] = 1
>>> dicty['A']['b'] = 2
>>> dicty
{'A': {'a': 1, 'b': 1}}

然后defaultdict所做的只是让我们不必测试我们是否已经创建了嵌套字典。而不是:

if outer not in dicty:
    dicty[outer] = {}
dicty[outer][inner] = value

我们只是省略if测试,因为如果密钥尚不存在defaultdict将为我们创建一个新字典。

虽然这不是做事的理想方式,但你已经非常接近让它发挥作用了。

您的主要问题是您正在重用相同的tmp字典。将其插入dicty的第一个键下后,clear它并开始用新值填充它。将tmp.clear()替换为 tmp = {} 来解决此问题,因此每个键都有不同的字典,而不是所有键都有相同的字典。

第二个问题是,当你到达最后时,你永远不会在字典中存储最后一个tmp值,所以在for循环之后再添加一个dicty[oldword] = tmp

你的第三个问题是你正在检查if oldword is not "":。即使它是一个空字符串,也可能是正确的,因为你是在比较身份,而不是平等。只需将其更改为 if oldword: .(这个,你通常会侥幸逃脱,因为小字符串通常会被拘留并且通常会共享身份......但你不应该指望这一点。

如果你同时修复这两个问题,你会得到这个:

{'FrontPage': {'frontpage': '0.710145', 'troubleshooting': '0.971014'},
 'proA': {'macbook': '0.666667', 'smart': '0.666667', 'ssd': '0.666667'}}

我不确定如何将其转换为您声称想要的格式,因为该格式甚至不是有效的字典。但希望这能让你接近。


有两种更简单的方法可以做到这一点:

  • 例如,将值分组,例如itertools.groupby,然后将每个组转换为字典,并在一个步骤中插入所有内容。这与现有代码一样,要求输入已由 values[0] 进行批处理。
  • 将字典用作字典。您可以在每个键进来时查找它,如果找到,则添加到值中,如果没有,则创建一个新键。defaultdictsetdefault方法将使它简洁,但即使你不知道这些,明确地写出来也很简单,而且它仍然不会像现在这样冗长。

第二个版本在Martijn Pieters的回答中已经解释得很好。

第一个可以这样写:

def doubleDict(s):
    with open(filename, "r") as f:
        rows = (line.rstrip().split(" ") for line in f)
        return {k: {values[1]: values[2] for values in g}
                for k, g in itertools.groupby(rows, key=operator.itemgetter(0))}

当然,这不会在每 25 行后打印出字典,但通过将理解转换为显式循环(理想情况下使用 enumerate 而不是保留显式row计数器(很容易添加。

最新更新