【问题标题】:Handle self-references when flattening dictionary扁平化字典时处理自引用
【发布时间】:2018-10-01 10:50:38
【问题描述】:

给定一些任意字典

mydict = {
    'first': {
        'second': {
            'third': {
                'fourth': 'the end'
             }
         }
     }
}

writing an answer到另一个问题的过程中,我写了一个小程序将其展平。

def recursive_flatten(mydict):
    d = {}
    for k, v in mydict.items():
        if isinstance(v, dict):
            for k2, v2 in recursive_flatten(v).items():
                d[k + '.' + k2] = v2 
        else:
            d[k] = v
    return d

它有效,给了我想要的东西:

new_dict = recursive_flatten(mydict)

print(new_dict)
{'first.second.third.fourth': 'the end'}

并且应该适用于几乎任何任意结构的字典。不幸的是,它没有:

mydict['new_key'] = mydict

现在recursive_flatten(mydict) 将一直运行到堆栈空间用完为止。我试图弄清楚如何优雅地处理自引用(基本上,忽略或删除它们)。更复杂的是,任何子词典都可能发生自我引用……而不仅仅是顶层。我将如何优雅地处理自引用?我可以想到一个可变的默认参数,但应该有更好的方法......对吗?

感谢指点,感谢阅读。如果您有任何其他建议/改进,我欢迎recursive_flatten

【问题讨论】:

  • 一种方法是将您遇到的所有内容都放入字典中,并在进行过程中检查您以前从未见过的内容。另一种方法是保留两个指针,让其中一个走两步,另一个走一步。如果它们重合,你就有了一个循环。问题还在于您接下来要做什么 - 中止或返回明确定义的内容。

标签: python dictionary recursion


【解决方案1】:

您可以使用setid 来做到这一点。请注意,此解决方案还使用生成器,这意味着我们可以开始使用扁平化的字典计算整个结果之前

def recursive_flatten (mydict):
  def loop (seen, path, value):

    # if we've seen this value, skip it
    if id(value) in seen:
      return

    # if we haven't seen this value, now we have
    else:
      seen.add(id(value))

    # if this value is a dict...
    if isinstance (value, dict):
      for (k, v) in value.items ():
        yield from loop(seen, path + [k], v)

    # base case
    else:
      yield (".".join(path), value)

  # init the loop    
  yield from loop (set(), [], mydict)

程序演示

mydict = {
    'first': {
        'second': {
            'third': {
                'fourth': 'the end'
             }
         }
     }
}

for (k,v) in recursive_flatten (mydict):
  print (k, v)

# first.second.third.fourth the end

mydict['new_key'] = mydict

for (k,v) in recursive_flatten (mydict):
  print (k, v)

# first.second.third.fourth the end

如果您想查看自引用值的输出,我们可以稍作修改

# if we've seen this value, skip it
if (id(value) in seen):
  # this is the new line
  yield (".".join(path), "*self-reference* %d" % id(value))
  return

现在程序的输出将是

first.second.third.fourth the end
first.second.third.fourth the end
new_key *self-reference* 139700111853032

【讨论】:

    【解决方案2】:

    我不确定您对“优雅”的定义是什么,但这可以通过记录之前在对象 ID 的 set 中看到的内容来完成:

    class RecursiveFlatten:
        def __init__(self):
            self.seen = set()
    
        def __call__(self, mydict):
            self.seen.add(id(mydict))
            d = {}
            for k, v in mydict.items():
                if isinstance(v, dict):
                    if id(v) not in self.seen:
                        self.seen.add(id(v))
                        for k2, v2 in self(v).items():
                            d[k + '.' + k2] = v2
                else:
                    d[k] = v
            return d
    
    def recursive_flatten(mydict):
        return RecursiveFlatten()(mydict)
    

    测试出来的结果是我所期望的

    mydict = {
        'first': {
            'second': {
                'third': {
                    'fourth': 'the end'
                 }
             },
            'second2': {
                'third2': 'the end2'
            }
         }
    }
    
    mydict['first']['second']['new_key'] = mydict
    mydict['new_key'] = mydict
    print(recursive_flatten(mydict))
    

    输出:

    {'first.second2.third2': 'the end2', 'first.second.third.fourth': 'the end'}
    

    【讨论】:

    • 在 Python 中使用这样的类很常见吗?本地化一个私有绑定有点过头了,不是吗?
    • 我以前做过,如果我说它是矫枉过正但想得更多,我可能会从嵌套函数中实现相同的效果。这就是我在 C++ 中生活了 3 年所得到的。无论如何,您的方法肯定更好
    • 不用担心。我自己是 Python 新手,所以我不知道社区认为什么是惯用的。很高兴看到一个对 OP 的原始代码修改最少的解决方案!
    猜你喜欢
    • 1970-01-01
    • 2021-12-08
    • 1970-01-01
    • 2011-08-27
    • 1970-01-01
    • 2014-05-17
    • 2022-10-24
    • 2019-03-02
    相关资源
    最近更新 更多