基于排除密钥的相似性合并两个词典

时间:2018-05-09 18:54:02

标签: python

我在数组中有以下三个词典:

items = [ 
{
    'FirstName': 'David',
    'LastName': 'Smith',
    'Language': set(['en'])
},

{
    'FirstName': 'David',
    'LastName': 'Smith',
    'Language': set(['fr'])
},

{
    'FirstName': 'Bob',
    'LastName': 'Jones',
    'Language': set(['en'])
} ]

如果两个词典相同,减去指定的键,我想将这些词典合并在一起:并将该键添加到一起。如果使用"Language"键,它会将数组合并到以下内容中:

[ {
    'FirstName': 'David',
    'LastName': 'Smith',
    'Language': set(['en','fr'])
},{
    'FirstName': 'Bob',
    'LastName': 'Jones',
    'Language': set(['en'])
} ]

以下是我目前正在做的事情:

from copy import deepcopy

def _merge_items_on_field(items, field):
    '''Given an array of dicts, merge the 
       dicts together if they are the same except for the 'field'.

       If merging dicts, add the unique values of that field together.'''

    items = deepcopy(items)
    items_merged_on_field = []

    for num, item in enumerate(items):

        # Remove that key/value from the dict
        field_value = item.pop(field)

        # Get an array of items *without* that field to compare against
        items_without_field = deepcopy(items_merged_on_field)
        map(lambda d: d.pop(field), items_without_field)

        # If the dict item is found ("else"), add the fields together
        # If not ("except"), then add in the dict item to the array
        try:
            index = items_without_field.index(item) 
        except ValueError:
            item[field] = field_value
            items_merged_on_field.append(item)
        else:
            items_merged_on_field[index][field] = items_merged_on_field[index][field].union(field_value)

    return items_merged_on_field

>>> items = [{'LastName': 'Smith', 'Language': set(['en']), 'FirstName': 'David'}, {'LastName': 'Smith', 'Language': set(['fr']), 'FirstName': 'David'}, {'LastName': 'Jones', 'Language': set(['en']), 'FirstName': 'Bob'}]
>>> _merge_items_on_field(items, 'Language')
[{'LastName': 'Smith', 'Language': set(['fr', 'en']), 'FirstName': 'David'}, {'LastName': 'Jones', 'Language': set(['en']), 'FirstName': 'Bob'}]

这看起来有点复杂 - 有更好的方法吗?

4 个答案:

答案 0 :(得分:4)

有几种方法可以做到这一点。据我所知,最无痛的方法是利用熊猫图书馆 - 特别是groupby + apply

import pandas as pd

merged = (
    pd.DataFrame(items)
      .groupby(['FirstName', 'LastName'], sort=False)
      .Language
      .apply(lambda x: set.union(*x))
      .reset_index()
      .to_dict(orient='records')
)

print(merged)
[
    {'FirstName': 'David', 'LastName': 'Smith', 'Language': {'en', 'fr'}},
    {'FirstName': 'Bob', 'LastName': 'Jones', 'Language': {'en'}}
]

另一种方法(我提到过)使用itertools.groupby,但看到你有30列要分组,我只是建议坚持使用pandas。

如果你想把它变成一个函数,

def merge(items, field):
    df = pd.DataFrame(items)
    columns = df.columns.difference([field]).tolist()
    return (
        df.groupby(columns, sort=False)[field]
          .apply(lambda x: set.union(*x))
          .reset_index()
          .to_dict(orient='records')
    )

merged = merge(items, 'Language')
print(merged)
[
    {'FirstName': 'David', 'LastName': 'Smith', 'Language': {'en', 'fr'}},
    {'FirstName': 'Bob', 'LastName': 'Jones', 'Language': {'en'}}
]

答案 1 :(得分:1)

您可以使用itertools.groupby

import itertools
d = [{'FirstName': 'David', 'LastName': 'Smith', 'Language': {'en'}}, {'FirstName': 'David', 'LastName': 'Smith', 'Language': {'fr'}}, {'FirstName': 'Bob', 'LastName': 'Jones', 'Language': {'en'}}]
v = [[a, list(b)] for a, b in itertools.groupby(sorted(d, key=lambda x:x['FirstName']), key=lambda x:x['FirstName'])]
final_dict = [{**{'FirstName':a}, **{'LastName':(lambda x:[list(set(x)), x[0]][len(set(x)) == 1])([i['LastName'] for i in b])}, **{'Language':set([list(i['Language'])[0] for i in b])}} for a, b in v]

输出:

[{'FirstName': 'Bob', 'LastName': 'Jones', 'Language': {'en'}}, {'FirstName': 'David', 'LastName': 'Smith', 'Language': {'en', 'fr'}}]

答案 2 :(得分:1)

如果pandas不是一个选项:

from itertools import groupby
from functools import reduce

arr = [
    {'FirstName': 'David', 'LastName': 'Smith', 'Language': set(['en'])},
    {'FirstName': 'David', 'LastName': 'Smith', 'Language': set(['fr'])},
    {'FirstName': 'David', 'LastName': 'Jones', 'Language': set(['sp'])}
]

def reduce_field(items, field, op=set.union, sort=False):

    def _key(d):
        return tuple((k, v) for k, v in d.items() if k != field)

    if sort:
        items = sorted(items, key=_key)
    res = []
    for k, g in groupby(items, key=_key):
        d = dict(k)
        d[field] = reduce(op, (el[field] for el in g))
        res.append(d)

    return res

reduce_field(arr, 'Language')

答案 3 :(得分:0)

您可以手动尝试:

new_dict={}
#
#
#
d = [{'FirstName': 'David', 'LastName': 'Smith', 'Language': {'en'}},
     {'FirstName': 'David', 'LastName': 'Smith', 'Language': {'fr'}},
     {'FirstName': 'Bob', 'LastName': 'Jones', 'Language': {'en'}}]




for i in d:
     if (i['FirstName'],i['LastName']) not in new_dict:
          new_dict[(i['FirstName'],i['LastName'])]=i
     else:
          new_dict[(i['FirstName'],i['LastName'])]['Language']=set(list(new_dict[(i['FirstName'],i['LastName'])]['Language'])+list(i['Language']))
print(new_dict.values())

输出:

# dict_values([{'FirstName': 'Bob', 
#               'LastName': 'Jones', 
#               'Language': {'en'}}, 
#              {'FirstName': 'David', 
#               'LastName': 'Smith', 
#                'Language': {'fr', 'en'}}])