如何创建索引某些键位置的字典?

How do I create a dictionary that indexes the location of certain keys?

我有一个 class 继承了 dict 对象。

my_subclassed_dict = SubclassedDictionary({
        "id": {"value1": 144
               "value2": "steve",
               "more" {"id": 114}
        },
        "attributes": "random"
})

SubclassedDictionary 初始化 中,我希望生成符合特定条件的路径。

假设,如果我要提出这个条件,'index all numbers above 100' 这可能会访问 my_subclassed_dict.get_paths(),然后 return 类似于这样的某种结构:

[
    ['id', 'value1'], 
    ['id', 'more', 'id',]
]

简而言之,如何在实例化时为匹配特定条件的键生成路径的 subclass dict

编辑

因为有人要求示例实现。然而,这个问题是它不处理嵌套字典。

class SubclassedDictionary(dict):
    paths = []

    def __init__(self, *args, **kwargs):
        self.update(*args, **kwargs)  # use the free update to set keys

    def update(self, *args, **kwargs):
        temp = args[0]
        for key, value in temp.items():
            if isinstance(value, int):
                if value > 100:
                    self.paths.append(key)
        super(SubclassedDictionary, self).update(*args, **kwargs)

dictionary = {
   "value1": 333,
   "v2": 99,
   "v2": 129,
   "v3": 30,
   "nested": {
      "nested_value" 1000
   }
}

new_dict = SubclassedDictionary(dictionary)

print(new_dict.paths) # outputs: ['v2','value1']

如果它按预期工作。

print(new_dict.paths) 

会输出

[
   ['v2'],
   ['value1'],
   ['nested', 'nested_value']
]

据我了解,如果键的关联值匹配特定条件,您需要一个能够返回字典中字典键的字典。

class SubclassedDictionary(dict):
    def __init__(self, new_dict, condition=None, *args, **kwargs):
        super(SubclassedDictionary, self).__init__(new_dict, *args, **kwargs)
        self.paths = []
        self.get_paths(condition)

    def _get_paths_recursive(self, condition, iterable, parent_path=[]):
        path = []
        for key, value in iterable.iteritems():
            # If we find an iterable, recursively obtain new paths.
            if isinstance(value, (dict, list, set, tuple)):
                # Make sure to remember where we have been (parent_path + [key])
                recursed_path = self._get_paths_recursive(condition, value, parent_path + [key])
                if recursed_path:
                    self.paths.append(parent_path + recursed_path)
            elif condition(value) is True:
                self.paths.append(parent_path + [key])

    def get_paths(self, condition=None):
        # Condition MUST be a function that returns a bool!
        self.paths = []
        if condition is not None:
            return self._get_paths_recursive(condition, self)

def my_condition(value):
    try:
        return int(value) > 100
    except ValueError:
        return False



my_dict = SubclassedDictionary({"id": {"value1": 144,
                                       "value2": "steve",
                                       "more": {"id": 114}},
                                "attributes": "random"},
                               condition=my_condition)

print my_dict.paths  # Returns [['id', 'value1'], ['id', 'more', 'id']]

此实施有一些好处。一是您可以随时更改您的条件。在您的问题中,听起来这可能是您感兴趣的功能。如果您想要不同的条件,您可以轻松编写一个新函数并将其传递给 class 的构造函数,或者简单地调用 get_paths() 你的新条件。

在开发递归算法时,您应该考虑 3 件事。

1) What is my stopping condition? 在这种情况下,您的文字条件实际上并不是您的停止条件。当不再有要迭代的元素时,递归停止。

2) Create a non-recursive function 这很重要有两个原因(我稍后会谈到第二个)。第一个原因是它是封装您不希望消费者使用的功能的安全方法。在这种情况下,_get_paths_recursive() 有额外的参数,如果消费者掌握了这些参数,可能会破坏您的路径属性。

3) Do as much error handling before recursion (Second reason behind two functions) 第二个函数的另一个好处是您可以执行非递归操作。通常情况下,当您编写递归算法时,您将不得不在开始递归之前做一些事情。在这种情况下,我确保 condition 参数有效(我可以添加更多检查以确保其函数 returns 是一个布尔值,并接受一个参数)。我还重置了路径属性,这样如果 get_paths() 被多次调用,您就不会得到大量的路径。

最小的变化是这样的:

class SubclassedDictionary(dict):

    def __init__(self, *args, **kwargs):
        self.paths = []  # note instance, not class, attribute
        self.update(*args, **kwargs)  # use the free update to set keys

    def update(self, *args, **kwargs):
        temp = args[0]
        for key, value in temp.items():
            if isinstance(value, int):
                if value > 100:
                    self.paths.append([key])  # note adding a list to the list
            # recursively handle nested dictionaries
            elif isinstance(value, dict):
                for path in SubclassedDictionary(value).paths:
                    self.paths.append([key]+path)
        super(SubclassedDictionary, self).update(*args, **kwargs)

它给出了您正在寻找的输出:

>>> SubclassedDictionary(dictionary).paths
[['v2'], ['value1'], ['nested', 'nested_value']]

然而,更简洁的方法可能是使 paths 成为一个方法,并创建嵌套的 SubclassedDictionary 实例而不是字典,这也允许您在调用时指定规则而不是硬编码它。例如:

class SubclassedDictionary(dict):

    def __init__(self, *args, **kwargs):
        self.update(*args, **kwargs)  # use the free update to set keys

    def update(self, *args, **kwargs):
        temp = args[0]
        for key, value in temp.items():
            if isinstance(value, dict):
                temp[key] = SubclassedDictionary(value)
        super(SubclassedDictionary, self).update(*args, **kwargs)

    def paths(self, rule):
        matching_paths = []
        for key, value in self.items():
            if isinstance(value, SubclassedDictionary):
                for path in value.paths(rule):
                    matching_paths.append([key]+path)
            elif rule(value):
                matching_paths.append([key])
        return matching_paths

在使用中,获取所有大于100的整数的路径:

>>> SubclassedDictionary(dictionary).paths(lambda val: isinstance(val, int) and val > 100)
[['v2'], ['value1'], ['nested', 'nested_value']]

一个缺点是每次调用时都会重新创建路径列表。


值得注意的是,您目前没有正确处理 kwargs(所以我的代码也没有!);看看例如 我在其中提供了一个答案,展示了如何实现与基本 dict 相匹配的接口。您当前代码的另一个问题是它不处理随后从字典中删除的键;我的第一个片段也没有,但是第二个片段每次都会重建路径列表,这不是问题。