【问题标题】:Combine dictionary entries by common elements按共同元素组合字典条目
【发布时间】:2014-11-17 11:56:56
【问题描述】:

我有一个非常大的字典,其中的键包含项目列表。我想以一种有效的方式对列表中至少有一个元素的所有键进行分组。例如:

dictionary = {'group1': ['a', 'b', 'c', 'd'], 
        'group2': ['a', 'b', 'c', 'd', 'e'], 
        'group3': ['f', 'g', 'h'], 
        'group4': ['g', 'z']}

group_dict(dictionary)

会回馈:

dictionary = {'group1': ['a', 'b', 'c', 'd', 'e'], 
        'group3': ['f', 'g', 'h', 'z']}

更新

字典的“真实”结构是:

dictionary = {'group1' :{'IDs': ['a','b','c','d'], 'oldest_node': 'node_30'}, 'group2' :{'IDs': ['c','d','e'], 'oldest_node': 'node_40'}, 'group3' :{'IDs': ['h','k'], 'oldest_node': 'node_2'}, 'group4' :{'IDs': ['z','w','x','j'], 'oldest_node': 'node_6'}, 'group3' :{'IDs': ['h','z'], 'oldest_node': 'node_9'}

我想生成最具包容性的组,并保持节点变量的最小值:

dictionary = {'group1' :{'IDs': ['a','b','c','d','e'], 'oldest_node': 'node_30'}, 'group3' :{'IDs': ['h','k','z','w','x','j'], 'oldest_node': 'node_2'}}

【问题讨论】:

  • 那么您的问题到底是什么?顺便说一句,不要将自己的字典命名为dict;你会隐藏内置的。
  • 案例组 5 : ['g', 'a'] 会如何影响事物?
  • 它将所有的键归为一组。
  • @johnsharpe 这只是一个示例字典。但你是对的,我改变它。
  • 我猜这与Exact Cover 问题有关。这很有趣,所以我可能会在我睡了一会儿之后尝试编写一个有效的解决方案。 :)

标签: python dictionary merge


【解决方案1】:

下面的程序解决了原来的问题。可能有一种更有效的算法,但我认为这个算法相当快。

修改此代码以应对问题更新版本中更复杂的dict应该不会太难。

(我使用的是 Python 2.6,所以我没有 dict 理解,这就是我使用生成器表达式构建 dicts 的原因)。

merge_lists.py

#! /usr/bin/env python

''' Merge lists in a dict 

    Lists are merged if they have any element in common,
    so that in the resulting dict no list element will be 
    associated with more than one key.

    Written by PM 2Ring 2014.11.18

    From http://stackoverflow.com/q/26972204/4014959
'''

#Some test data
groups = {
    'g01': ['a', 'b', 'c', 'd'], 
    'g02': ['a', 'b', 'c', 'd', 'e'], 
    'g03': ['f', 'g', 'h'], 
    'g04': ['g', 'j'],
    #'g05': ['g', 'a'],
    'g06': ['k', 'l'],
    'g07': ['l', 'm'],
    'g08': ['m', 'n'],
    'g09': ['o', 'p'],
    'g10': ['p', 'q'],
    'g11': ['q', 'o'],
    #'g12': ['l', 'q'],
}


def merge_lists(d):
    src = dict((k, set(v)) for k, v in d.iteritems())

    while True:
        dest = {}
        count = 0
        while src:
            k1, temp = src.popitem()
            if temp is None:
                continue
            for k2, v in src.iteritems():
                if v is None:
                    continue
                if temp & v:
                    temp |= v
                    src[k2] = None
                    count += 1
                    k1 = min(k1, k2)
            dest[k1] = temp

        if count > 0:
            #print count
            #print_dict(dest)
            src = dest
        else:
            dest = dict((k, sorted(list(v))) for k, v in dest.iteritems())
            return dest


def print_dict(d):
    for k in sorted(d.keys()):
        print "%s: %s" % (k, d[k])
    print


def main():
    print_dict(groups)
    print 20*'-'

    dest = merge_lists(groups)
    print_dict(dest)


if __name__ == '__main__':  
    main()

输出

g02: ['a', 'b', 'c', 'd', 'e']
g03: ['f', 'g', 'h']
g04: ['g', 'j']
g06: ['k', 'l']
g07: ['l', 'm']
g08: ['m', 'n']
g09: ['o', 'p']
g10: ['p', 'q']
g11: ['q', 'o']

--------------------
g01: ['a', 'b', 'c', 'd', 'e']
g03: ['f', 'g', 'h', 'j']
g06: ['k', 'l', 'm', 'n']
g09: ['o', 'p', 'q']

这是一个适用于更新后的 dict 结构的版本。

#! /usr/bin/env python

''' Merge lists in a dict 

    Lists are merged if they have any element in common,
    so that in the resulting dict no list element will be 
    associated with more than one key.

    The key of the merged item is selected from the sub-dict with the lowest
    value of oldest_node.

    Written by PM 2Ring 2014.11.21

    From http://stackoverflow.com/q/26972204/4014959
'''

#Some test data

groups = {
    'group1': {'IDs': ['a','b','c','d'], 'oldest_node': 'node_30'}, 
    'group2': {'IDs': ['c','d','e'], 'oldest_node': 'node_40'}, 
    'group3': {'IDs': ['h','k'], 'oldest_node': 'node_2'}, 
    'group4': {'IDs': ['z','w','x','j'], 'oldest_node': 'node_6'}, 
    'group5': {'IDs': ['h','z'], 'oldest_node': 'node_9'},
}


def merge_lists(d):
    #Convert IDs to a set and oldest_node to an int 
    src = {}
    for k, v in d.iteritems():
        src[k] = {
            'IDs': set(v['IDs']), 
            'oldest_node': int(v['oldest_node'][5:])
        } 
    #print_dict(src)

    while True:
        dest = {}
        count = 0
        while src:
            k1, temp = src.popitem()
            if temp is None:
                continue
            for k2, v in src.iteritems():
                if v is None:
                    continue
                if temp['IDs'] & v['IDs']:
                    #Merge IDs
                    temp['IDs'] |= v['IDs']

                    #Determine key of merge from oldest_node
                    if v['oldest_node'] < temp['oldest_node']:
                        k1 = k2
                        temp['oldest_node'] = v['oldest_node']
                    src[k2] = None
                    count += 1
            dest[k1] = temp

        src = dest

        #Exit loop if no changes occured 
        if count == 0:
            break
        else:
            #print count
            #print_dict(src)
            pass

    #Convert dict back to original form
    dest = {}
    for k, v in src.iteritems():
        dest[k] = {
            'IDs': sorted(list(v['IDs'])), 
            'oldest_node': 'node_%d' % v['oldest_node']
        }
    return dest


def print_dict(d):
    for k in sorted(d.keys()):
        print "%s: %s" % (k, d[k])
    print


def main():
    print_dict(groups)
    print 20*'-'

    dest = merge_lists(groups)
    print_dict(dest)


if __name__ == '__main__':  
    main()

输出

group1: {'IDs': ['a', 'b', 'c', 'd'], 'oldest_node': 'node_30'}
group2: {'IDs': ['c', 'd', 'e'], 'oldest_node': 'node_40'}
group3: {'IDs': ['h', 'k'], 'oldest_node': 'node_2'}
group4: {'IDs': ['z', 'w', 'x', 'j'], 'oldest_node': 'node_6'}
group5: {'IDs': ['h', 'z'], 'oldest_node': 'node_9'}

--------------------
group1: {'IDs': ['a', 'b', 'c', 'd', 'e'], 'oldest_node': 'node_30'}
group3: {'IDs': ['h', 'j', 'k', 'w', 'x', 'z'], 'oldest_node': 'node_2'}

【讨论】:

  • 有效,速度合理。我正在尝试为“更新的结构”调整代码。到目前为止我还不是很成功。
  • @biojl:好的。我明天再考虑。
  • @biojl:如果您对新版本有什么不明白的地方,请告诉我。顺便说一句,你欠我很多。 :)
  • 我欠你的!非常感谢,你拯救了我的一天!
【解决方案2】:

您是否真的要更改用作输入的字典,或者如果您的函数输出另一个字典作为结果,是否可以?

这是一个快速而肮脏的函数,它将值分组:

def group_dict(d):
    result = {}
    for k1 in d:
        for k2 in d:
            if k1 != k2 and set(d.get(k1)).intersection(d.get(k2)):
                result[k1] = list(set(d.get(k1)).union(d.get(k2)))
    return result

它应该返回:

{'group1': ['a', 'c', 'b', 'e', 'd'],
 'group2': ['a', 'c', 'b', 'e', 'd'],
 'group3': ['h', 'z', 'g', 'f'],
 'group4': ['h', 'z', 'g', 'f']}

扩展功能以删除重复项。

我正在使用内置的set 及其方法intersectionunion。这应该是您的核心要求的关键。 在双 for 循环(非常丑陋)中,比较字典中的值,如果找到交集,则将值的并集转换为列表并分配给结果字典。这确实不是一个很好的解决方案,但也许它可以给你一些想法。

【讨论】:

  • 没关系,我可以生成一个新的字典。删除重复项会稍微复杂一些,因为我必须根据字典中另一个变量的值来决定要保留哪个副本。有没有一种简单的方法来检索具有相同值的所有键?我会根据字典中的其他变量轻松决定。
  • 我不知道任何在字典中查找重复值的内置函数。您可以尝试使用d.get(k1) in d1.values(),其中d 是字典,k1 是键。它将获取k1 的值并检查它是否已经在字典d1 中。您可以在此之前将字典d 复制到d1 并制作d1.remove(k1)
  • @user3684792 提议的案例会发生什么(案例组 5 : ['g', 'a'] 会如何影响事情?)。在您的代码中,它将被 group3 或 group1 吸收,但不会将它们全部组合在一个组中,对吗?
  • 在编写示例时,我没有考虑“group5”的情况。肯定会把事情搞砸的。你的假设是对的。它将“group1”、“group2”和“group5”放在一起,但会省略“group3”和“group4”,它实际上应该将所有组合并在一起。字典的主要问题是只有一个可能的键。也许您可以生成一些中间列表或元组,最后生成所需的字典。但这不会很简单。
  • @biojl:上面的代码只能合并成对的项目,所以它不能处理多个项目之间更复杂的关系。您可以通过多个周期运行它以改进合并,但这使得它成为 O(n^3),这对于大字典来说会很慢。
猜你喜欢
  • 2017-10-17
  • 1970-01-01
  • 1970-01-01
  • 2016-04-07
  • 2013-09-11
  • 2015-02-12
  • 1970-01-01
  • 2020-12-24
  • 2022-07-26
相关资源
最近更新 更多