【问题标题】:Find minimum number of keys which make each dict unique amongst multiple ones找到使每个字典在多个字典中唯一的最小键数
【发布时间】:2023-04-08 15:49:01
【问题描述】:

我正在尝试为以下问题找到一个有效的解决方案:

我有一个字典列表,每个字典具有与另一个字典相同的键集。关联值可以是相等的跨字典。我正在尝试找到 最小键数 及其关联值,这将使 each 字典唯一。

例如对于包含三个字典的列表:

list = [a, b, c]

where 

a = {"key1": "alpha", "key2": "beta", "key3": "gamma"}
b = {"key1": "alpha", "key2": "beta", "key3": "eta"}
c = {"key1": "alpha", "key2": "zeta", "key3": "eta"}

所有三个字典都具有相同的 key1 值,因此可以删除该键,因为它的包含并不能确定字典的唯一性。另一方面,必须同时包含 key2 和 key3,因为它们的集合使各自的字典是唯一的。

a = {"key2": "beta", "key3": "gamma"}
b = {"key2": "beta", "key3": "eta"}
c = {"key2": "zeta", "key3": "eta"}

我假设我循环遍历字典列表,因此可以在迭代中使用例如 collections.Counter。 列表中的字典数量与键的数量一起是一个变量。我想尽可能少地遍历列表(例如在更新一个或多个计数器时一次?)。我相当确定有一个合适的算法来解决这个问题,但我的搜索关键字找不到它。

编辑:每个最终字典必须具有与其他字典相同的键。因此,为每个单独的 dict 保留一组不同的键不是一种选择。

【问题讨论】:

  • 每个dict 是否有相同的keys
  • 为什么a 不是gammab 只是betac 只是zeta
  • @Raj 是的,每个字典都有相同的键
  • @blueteeth 我将编辑我的问题以突出这一方面,但每个最终字典必须具有与其他字典相同的键。因此,保留 a 的 key3 和 b 和 c 的 key2 不是一种选择。
  • 那么问题真的是如何删除所有变量中相同的键吗?

标签: python algorithm dictionary filter set


【解决方案1】:

一个精确的解决方案是 NP 难的,但要获得适当的近似值,您可以尝试使用 ID3 算法的变体来创建决策树:https://en.wikipedia.org/wiki/ID3_algorithm

你的情况不同的是你必须在所有分支中选择相同的属性,所以它会像这样工作:

  1. 从所有字典中的一组开始
  2. 对于每个属性,计算其值在所有集合中的熵总和。 公式在链接的文章中。
  3. 根据选择的属性对集合进行分区,丢弃所有只包含一个字典的集合
  4. 如果还有要分区的集合,返回(2)

【讨论】:

    【解决方案2】:

    我很高兴其他答案证实了我的怀疑,这是一个 NP 完全问题。目前没有已知的解决方法,在最坏的情况下,尝试所有可能的键子集。

    这是我的算法,它在O(n^2*2^k) 时间和O(nk^2+2^k) 空间中运行,其中n 是列表中的项目数,k 是每个项目的属性数。

    只要2^k n^2,这将在大致多项式时间内运行。

    def get_unique_key_values(objs):
        key = get_unique_key(objs)
        return [{ k: obj[k] for k in key } for obj in objs ]
    
    def get_unique_key(objs):
        return get_unique_key_set(objs, { k for obj in objs for k in obj }, [])
    
    def get_unique_key_set(objs, keys, tested_keys):
        if len(keys) == 0 or not all_unique(objs):
            # keys is either the empty set, or this subset of keys
            # does not guarantee uniqueness
            return False
    
        # the smallest number of keys required for a unique key
        best_key_set = set(keys)
    
        # delete each key one at a time and check if the list of 
        # items are still unique
        for del_key in keys:
            tmp_keys = set(keys)
            tmp_keys.remove(del_key)
    
            # if we've already tested this subset, skip it and all its children
            if tmp_keys in tested_keys:
                continue
    
            # keep track of subsets we've tested so we don't retest them--significant trimming
            tested_keys.append(tmp_keys)
    
            # generate a list of objects with only the current set of keys
            tmp_objs = [{ k: obj[k] for k in tmp_keys } for obj in objs]
    
            # continue to delete keys from the current subset until we find a subset
            # of size 1, or the current tmp_keys is optimal
            tmp_key_set = get_unique_key_set(tmp_objs, tmp_keys, tested_keys)
            if tmp_key_set == False:
                continue
    
            if len(tmp_key_set) < len(best_key_set):
                best_key_set = tmp_key_set
    
        return best_key_set
    
    # O(n^2) algorithm for checking that every element in the list is unique 
    def all_unique(objs):
        for i in range(len(objs) - 1):
            for j in range(i + 1, len(objs)):
                if objs[i] == objs[j]:
                    return False
    
        return True
    
    objects = [
        { 'a': 1, 'b': 2, 'c': 2 },
        { 'a': 1, 'b': 3, 'c': 2 },
        { 'a': 1, 'b': 3, 'c': 3 }
    ]
    
    print(get_unique_key(objects))
    # prints set([ 'b', 'c' ])
    
    objects = [
        { 'a': 1, 'b': 2, 'c': 2 },
        { 'a': 2, 'b': 3, 'c': 2 },
        { 'a': 3, 'b': 3, 'c': 3 }
    ]
    
    print(get_unique_key(objects))
    # prints set([ 'a' ])
    

    我在编写这个脚本时做了一些假设,例如所有对象都具有相同的属性集。如果某些属性只存在于某些对象上,您可能需要对脚本进行一些更改。

    您可以通过将tested_keys 更改为一个集合并为键数组创建一个散列函数并将散列存储在集合中来加快这一速度。

    为对象字典创建哈希函数可以将all_unique 转换为O(n) 算法,从而将总运行时间减少到O(n2^k)。具有讽刺意味的是,在显着减少运行时间的同时,它增加了它将成为指数时间算法的可能性,因为2^k &lt; n 更难以满足。

    请参阅 this answer 了解如何创建这些哈希。

    【讨论】:

      【解决方案3】:

      这个问题是NP-complete,通过减少到set cover problem 和从set cover problem。给定您问题的一个实例,我们可以在多项式时间内构造集合覆盖问题的多项式大小的实例,反之亦然。

      • 为了减少设置覆盖的问题,请获取所有无序对的集合,例如 (a,b)、(a,c)、(b,c);并且对于每个键,构造该键区分的对集合。区分所有原始字典对的最小键集是这些集的最小选择。

      • 为了减少对问题的集合覆盖,给定一个集合 { 1, 2, ..., n } 和一个子集的集合,构造 2n 个名为 a1, b1, a2, b2, ..., an, bn。对于每个子集,添加一个键,使得该键在每个字典 bk 中的值为 1,其中 k 在子集中,而在每个其他字典中的值为 0。在每个字典 ak 和 bk 中再添加一个值为 k 的键。区分所有对的最小键集必然包括最后一个键,但其余键对应于原始集覆盖实例的最小集选择。

      因此,没有已知的算法可以在多项式时间内解决您的问题。您的问题可以通过backtracking search 解决,但您不太可能找到比回溯更有效的算法。

      【讨论】:

        【解决方案4】:

        如果每个最终 dict 必须与所有其他 dicts 具有相同的键,唯一的解决方案是删除所有 dicts 中相同的键。

        您可以通过循环遍历第一个 dict 并将所有相同的键添加到列表中来做到这一点。然后在最后从字典中删除已保存列表中的所有键。

        def process_lists(lsts):
            first = lsts[0]
            to_remove = []
            for key in first:
                if all(first[key] == o[key] for o in lsts[1:]):
                   to_remove.append(key)
            return [{k: v for k, v in lst.items() if k not in to_remove} for lst in lsts]
        
        process_lists([a, b, c])
        

        【讨论】:

        • 不一定。假设 a 由唯一值组成,例如 id,&lt;b, c&gt; 组成唯一值。 a 就足够了,即使 bc 对于所有元素都不相同。
        • @dx_over_dt 在这种情况下,是否有可能比遍历所有2^n 键子集更好?
        • @Raj 这是我能想到的唯一算法,并进行了一些调整。首先,我们将删除每个元素中不存在的每个键。剩下的将是一个相当讨厌的深度优先搜索,其中每个深度都会删除一个额外的键。只要结果键形成相同数量元素的字典,就可以继续删除键。跟踪最大深度及其未移除的关键点。完成搜索后,未删除的键列表是最终答案,然后您遍历原始字典,将键/值对拉入它们自己的字典中。
        • 还有一种广度优先的方法,您可以递归地添加键,直到结果字典包含与原始字典相同数量的元素。这需要更多内存,因为您需要跟踪间歇性字典。哪种方法更快取决于最终答案中的键数。您可能能够并行运行这两种算法,您可以在 bfs 方法中添加当前密钥并从 dfs 方法中删除当前密钥。一旦一种算法产生了答案,你就完成了。
        猜你喜欢
        • 1970-01-01
        • 2021-03-14
        • 2022-11-26
        • 2023-04-06
        • 2021-12-19
        • 2017-01-04
        • 2019-10-05
        • 1970-01-01
        • 2020-03-11
        相关资源
        最近更新 更多