我有一本看起来像这样的字典
{
"key1": ["1", "3", "4", "7"],
"key2": ["7", "3", "2", "1"],
"key3": ["5", "2", "3", "1"],
"key4": ["4", "5", "1", "3"]
}
它是一个示例字典,真正的字典要大得多。 我需要按照列表中的重复项对键进行分组,输出结果如下所示:
key1
和key2
在各自的列表中都有"1"
,"7"
和"3"
,因此必须将它们归为group3.1
- 这意味着这些密钥在其列表中有3个常用项目,它是第一组(这里有3个常见项目)。
key2
和key3
都有" 3"," 2"和" 1"列表中的项目,因此这些键属于group3.2 - 这意味着这些键在列表中有3个常用项目,这是第二组此类
等等......
只有在列表中有两个或更多公共项目时才可以对密钥进行分组。这些组的顺序并不重要,因此group3.1可以是celled group3.2,group3.2可以称为group3.1等。唯一重要的是组名中的第一个数字表示有多少给定键的项目有共同点。
所有这一切的要点是我需要知道哪些密钥在其列表中的类似项目中有多少其他共同密钥。有点了解彼此之间的关系有多紧密。
我想要的输出结果是一个看起来像这样的字典
{
"group3.1": ["key1", "key2"],
"group3.2": ["key2", "key3"],
"group2.1": ["key1", "key3", "key4"]
}
非常感谢任何帮助!
答案 0 :(得分:1)
所以,我想不出一个聪明的解决方案,不需要将每个值集与每个其他值集进行比较。我不认为这是非常高效的,但它可能就足够了。首先,让我们从字典开始:
In [12]: d
Out[12]:
{'key1': ['1', '3', '4', '7'],
'key2': ['7', '3', '2', '1'],
'key3': ['5', '2', '3', '1'],
'key4': ['4', '5', '1', '3']}
现在,我们会将列表更改为frozensets
In [14]: ds = {k:frozenset(v) for k,v in d.items()}
In [16]: ds
Out[16]:
{'key1': frozenset({'1', '3', '4', '7'}),
'key2': frozenset({'1', '2', '3', '7'}),
'key3': frozenset({'1', '2', '3', '5'}),
'key4': frozenset({'1', '3', '4', '5'})}
这允许我们获得交叉点并且它是可以清除的:
In [17]: ds['key1'] & ds['key2']
Out[17]: frozenset({'1', '3', '7'})
In [18]: inverse = {}
所以,现在我们创建一个中间体。这将花费最长的处理时间:
In [20]: import itertools
In [21]: for k1, k2 in itertools.combinations(ds, 2):
...: inverse.setdefault(ds[k1] & ds[k2], []).extend((k1, k2))
...:
In [22]: inverse
Out[22]:
{frozenset({'1', '3'}): ['key1', 'key3', 'key2', 'key4'],
frozenset({'1', '2', '3'}): ['key2', 'key3'],
frozenset({'1', '3', '7'}): ['key1', 'key2'],
frozenset({'1', '3', '5'}): ['key3', 'key4'],
frozenset({'1', '3', '4'}): ['key1', 'key4']}
所以,现在我们实际上已经拥有了你想要的数据,但是现在有了你想要的密钥方案。要做到这一点,我们可以做类似的事情:
In [23]: from collections import defaultdict
In [24]: lenmap = defaultdict(lambda: itertools.count(1))
最后:
In [25]: inverse2 = {}
...: for k, v in inverse.items():
...: inverse2["group{}.{}".format(len(k), next(lenmap[len(k)]))] = v
...:
In [26]: inverse2
Out[26]:
{'group2.1': ['key1', 'key3', 'key2', 'key4'],
'group3.1': ['key2', 'key3'],
'group3.2': ['key1', 'key2'],
'group3.3': ['key3', 'key4'],
'group3.4': ['key1', 'key4']}
不幸的是,创建inverse
的步骤将是多项式时间。它至少为O(n ^ 2),其中N是字典的大小,但实际上,我认为它是O(n ^ 2 * m)其中m是键之间成对最小长度的最大值。可能不太可怕。 编辑在评论中注意到每个值都是10个网址的列表。在那种情况下,第二个中间步骤仅为O(n ^ 2),因为m是常数。
答案 1 :(得分:0)
由于您有许多密钥,但密钥只有10个URL,因此可能的优化方法是:
根据网址分配,它可能不会更改任何内容,也可能会删除很大一部分密钥。
与强力,二次解决方案相比,这种预处理速度非常快,所以它可能是值得的。
from collections import defaultdict, Counter
keys_by_url = defaultdict(list)
data = {
"key1": ["1", "3", "4", "7"],
"key2": ["7", "3", "2", "1"],
"key3": ["5", "2", "3", "1"],
"key4": ["4", "5", "1", "3"],
"key5": ["8", "9", "x", "3"],
"key6": ["a", "b", "c", "d"]
}
for key, urls in data.items():
for url in urls:
keys_by_url[url].append(key)
# defaultdict(<type 'list'>, {'a': ['key6'], 'c': ['key6'], 'b': ['key6'],
# 'd': ['key6'], '1': ['key3', 'key2', 'key1', 'key4'], '3': ['key3',
# 'key2', 'key1', 'key5', 'key4'], '2': ['key3', 'key2'], '5': ['key3',
# 'key4'], '4': ['key1', 'key4'], '7': ['key2', 'key1'], 'x': ['key5'],
# '9': ['key5'], '8': ['key5']})
for url, keys in keys_by_url.items():
if len(keys) == 1:
unique_key = keys[0]
data[unique_key].remove(url)
# {'key3': ['5', '2', '3', '1'], 'key2': ['7', '3', '2', '1'], 'key1': ['1', '3', '4', '7'], 'key6': [], 'key5': ['3'], 'key4': ['4', '5', '1', '3']}
trimmed_data = {key: values for key,
values in data.items() if len(values) >= 2}
# {'key3': ['5', '2', '3', '1'], 'key2': ['7', '3', '2', '1'], 'key1': ['1', '3', '4', '7'], 'key4': ['4', '5', '1', '3']}
在上面的示例中,由于预处理,key5
和key6
已从原始字典中删除。
对于每个密钥,您可以重复使用keys_by_url
来查找至少包含2个网址的密钥:
for key, urls in trimmed_data.items():
possible_keys = Counter(
[other_key for url in urls for other_key in keys_by_url[url] if other_key > key])
print(key)
print([k for k in possible_keys if possible_keys[k] > 1])
print("---")
输出:
key3
['key4']
---
key4
[]
---
key1
['key3', 'key4', 'key2']
---
key2
['key3', 'key4']
---
这应该非常快,只会留下有趣的密钥对。然后,您可以在@ juanpa.arrivillaga的solution而不是itertools.combinations
中使用这些对。
from collections import defaultdict, Counter
keys_by_url = defaultdict(list)
data = {
"key1": ["1", "3", "4", "7"],
"key2": ["7", "3", "2", "1"],
"key3": ["5", "2", "3", "1"],
"key4": ["4", "5", "1", "3"],
"key5": ["8", "9", "x", "3"],
"key6": ["a", "b", "c", "d"]
}
for key, urls in data.items():
for url in urls:
keys_by_url[url].append(key)
for key, urls in data.items():
possible_keys = Counter(
[other_key for url in urls for other_key in keys_by_url[url] if other_key > key])
at_least_2_common_urls = [k for k in possible_keys if possible_keys[k] > 1]
if at_least_2_common_urls:
print(key)
print(at_least_2_common_urls)
print('---')
输出:
key1
['key4', 'key3', 'key2']
---
key3
['key4']
---
key2
['key4', 'key3']
---