【问题标题】:Recursive function to check dictionary is a subset of another dictionary检查字典的递归函数是另一个字典的子集
【发布时间】:2018-08-31 08:23:06
【问题描述】:

我想递归地检查一个字典是否是另一个字典的子集。让我们假设两个字典都有内置类型作为项目。

我已经看到已经有一个非常古老的线程 Python: Check if one dictionary is a subset of another larger dictionary 试图解决类似但不完全的问题......因为那里的答案都不能满足我的目的,所以我决定发布我自己的 @987654322 @ 在那里,但它仍然没有完全完成,下面的函数在几乎所有情况下都可以正常工作,但在子集具有超集中不存在的值的情况下,它会惨遭失败,即:

def is_subset(superset, subset):
    for key, value in superset.items():
        if key not in subset:
            continue

        if isinstance(value, dict):
            if not is_subset(value, subset[key]):
                return False
        elif isinstance(value, str):
            if not subset[key] in value:
                return False
        elif isinstance(value, list):
            if not set(subset[key]) <= set(value):
                return False
        elif isinstance(value, set):
            if not subset[key] <= value:
                return False
        else:
            if not subset[key] == value:
                return False

    return True

superset = {'question': 'mcve', 'metadata': {}}
subset = {'question': 'mcve', 'metadata': {'author': 'BPL'}}

print(is_subset(superset, subset))

该函数返回 True,但它应该返回 False。那么,你将如何解决它?

【问题讨论】:

  • 值是子字符串是可以接受的,对吗?
  • 如果 value 是一个字符串,你希望你的子集字典是一个子字符串,当你在 value 中使用 subset[key] 时,是正确的行为

标签: python dictionary


【解决方案1】:

我不太喜欢最初的解决方案 - 正如一些 cmets 所说,它不适用于我的一些案例。这是一个更通用的解决方案:

def is_subvalue(supervalue, subvalue) -> bool:
    """Meant for comparing dictionaries, mainly.

    Note - I don't treat ['a'] as a subvalue of ['a', 'b'], or 'a' as a subvalue of 'ab'.
    For that behavior for a list or set, remove the line: `if len(supervalue) != len(subvalue): return False`
    For that behavior for a string, switch `subvalue == supervalue` to `subvalue in supervalue` for strings only.
    But NOT in this function, as it's meant to compare dictionaries and {'ab': 'a'} is not the same as {'a': 'a'}
    """
    if isinstance(subvalue, list) or isinstance(subvalue, set):
        if isinstance(subvalue, list) and not isinstance(supervalue, list):
            return False
        if isinstance(subvalue, set) and not isinstance(supervalue, set):
            return False
        if len(supervalue) != len(subvalue):
            return False
        return all([is_subvalue(supervalue[i], subvalue[i]) for i in range(len(subvalue))])
    if isinstance(subvalue, dict):
        if not isinstance(supervalue, dict):
            return False
        for key in subvalue:
            if key not in supervalue or not is_subvalue(supervalue[key], subvalue[key]):
                return False
        return True
    # all other types.
    return supervalue == subvalue

这里有一些测试:

assert is_subvalue(None, None)
assert is_subvalue(1, 1)
assert is_subvalue('1', '1')
assert is_subvalue([], [])
assert is_subvalue({}, {})
assert is_subvalue(['1'], ['1'])
assert not is_subvalue(['1'], ['1', '2']) and not is_subvalue(['1', '2'], ['1'])
assert is_subvalue({'a': 'b'}, {'a': 'b'})
assert not is_subvalue({'ab': 'b'}, {'a': 'b'})
assert is_subvalue({'a': ['b']}, {'a': ['b']})
assert is_subvalue({'a': ['b', 'c']}, {'a': ['b', 'c']})
# tests for ensuring more complex dictionary checks work
assert not is_subvalue({'a': ['b', 'c']}, {'a': ['b']})
assert not is_subvalue({'a': 'b'}, {'a': 'b', 'b': 'c'})
assert is_subvalue({'a': 'b', 'b': 'c', 'c': 'd'}, {'a': 'b', 'b': 'c'})
assert not is_subvalue({'a': 'b', 'b': 'c', 'c': 'd'}, {'a': 'b', 'b': {'c': 'c'}})
assert is_subvalue({'a': 'b', 'b': {'c': 'c'}, 'c': 'd'}, {'a': 'b', 'b': {'c': 'c'}})
assert is_subvalue({'a': 'b', 'b': ['c', 'c'], 'c': 'd'}, {'a': 'b', 'b': ['c', 'c']})

【讨论】:

    【解决方案2】:

    这是一个也可以正确递归到列表和集合中的解决方案。 (我改变了参数的顺序,因为这对我来说更有意义)

    def is_subset(subset, superset):
        if isinstance(subset, dict):
            return all(key in superset and is_subset(val, superset[key]) for key, val in subset.items())
    
        if isinstance(subset, list) or isinstance(subset, set):
            return all(any(is_subset(subitem, superitem) for superitem in superset) for subitem in subset)
    
        if isinstance(subset, str):
            return subset in superset
    
        # assume that subset is a plain value if none of the above match
        return subset == superset
    

    【讨论】:

      【解决方案3】:

      您的代码逻辑颠倒了。请注意您如何获取 超集 中的每个元素,如果 它们不在子集中,则继续。您要做的是获取 子集 中的每个元素并检查 它们是否在超集中

      这是您的代码的固定版本。

      def is_subset(superset, subset):
          for key, value in subset.items():
              if key not in superset:
                  return False
      
              if isinstance(value, dict):
                  if not is_subset(superset[key], value):
                      return False
      
              elif isinstance(value, str):
                  if value not in superset[key]:
                      return False
      
              elif isinstance(value, list):
                  if not set(value) <= set(superset[key]):
                      return False
              elif isinstance(value, set):
                  if not value <= superset[key]:
                      return False
      
              else:
                  if not value == superset[key]:
                      return False
      
          return True
      

      以下是给出正确结果的函数的一些示例。

      superset = {'question': 'mcve', 'metadata': {}}
      subset = {'question': 'mcve', 'metadata': {'author': 'BPL'}}
      
      is_subset(superset, subset) # False
      
      superset = {'question': 'mcve', 'metadata': {'foo': {'bar': 'baz'}}}
      subset = {'metadata': {'foo': {}}}
      
      is_subset(superset, subset) # True
      
      superset = {'question': 'mcve', 'metadata': {'foo': 'bar'}}
      subset = {'question': 'mcve', 'metadata': {}, 'baz': 'spam'}
      
      is_subset(superset, subset) # False
      

      【讨论】:

      • 我只是觉得你选择将{'foo': 'b'} 视为{'foo': 'bar'} 的一个子集很奇怪。除此之外,您可能希望嵌套在集合和列表中的字典具有更完整的行为,但这也可能超出您的用例范围。
      • 只要您意识到这种行为,这并不奇怪。如果它适合您的用例,那就完美了。
      【解决方案4】:

      只是一个猜测,但我认为因为超集中“元数据”返回的 dic 是空的,所以没有任何 if 语句返回 true,所以你得到了 True 的最终返回。

      您可以检查一下任一 dic 的长度是否为零。如果一个不是另一个,则返回 false。否则继续你的递归解决方案。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-06-02
        • 2021-01-18
        • 2012-03-08
        • 2021-12-07
        • 1970-01-01
        • 2019-03-17
        • 2021-09-26
        • 2014-09-16
        相关资源
        最近更新 更多