coder
coder

Reputation: 23

Combine lists of dictionaries with different values

I have list of dictionaries with matching keys like this.

[{'account_general_permission': {'view': False, 'create': False, 'edit': False, 'delete': False}}, 
{'control_section_permission': {'view': False, 'create': False, 'edit': False, 'delete': False}}, 
{'account_general_permission': {'view': True, 'create': True, 'edit': True, 'delete': False}}, 
{'control_section_permission': {'view': False, 'create': False, 'edit': False, 'delete': False}}]

I need to combine all matching keys together with the value inside the dicts where if there is true for some key its given priority over false.

So result should look something like

{'account_general_permission': {'view': True, 'create': True, 'edit': True, 'delete': False}}, 
{'control_section_permission': {'view': False, 'create': False, 'edit': False, 'delete': False}}]

What would the fastest way to achieve this as speed is crucial to perform this.

My attempt Assuming list is sort based on keys and I have a variable x to know how many times a key repeats in a list


new_list = np.array_split(new_list, len(permission))

for i in new_list:
    for j in i:
        for k, l in j.items():
            for m, n in l.items():
                if n == True:
                    l[m] = n

This partially works, also doesn't look the cleanest.

Upvotes: 0

Views: 96

Answers (5)

Nikolaj Š.
Nikolaj Š.

Reputation: 1986

Let's get crazy and functional:

lod = [{'account_general_permission': {'view': False, 'create': False, 'edit':
                                       False, 'delete': False}},
       {'control_section_permission': {'view': False, 'create': False, 'edit':
                                       False, 'delete': False}},
       {'account_general_permission': {'view': True, 'create': True, 'edit':
                                       True, 'delete': False}},
       {'control_section_permission': {'view': False, 'create': False, 'edit':
                                       False, 'delete': False}}]

from functools import reduce
from itertools import groupby


def get_key(d):
    return next(iter(d.keys()))


def get_val(d):
    return next(iter(d.values()))


def merge_dicts(d, perm_d):
    unwrappe_d = get_val(perm_d)
    keys = set(d) | set(unwrappe_d)
    return {p: any(d.get(p) for d in (d, unwrappe_d))
            for p in keys}



grouped = groupby(sorted(lod, key=get_key), get_key)

result = [{p: reduce(merge_dicts, perm_dicts, {})}
          for p, perm_dicts in grouped]

Upvotes: 0

Lucas M. Uriarte
Lucas M. Uriarte

Reputation: 3101

this should work

from collections import Counter

data = [{'account_general_permission': {'view': False, 'create': False, 'edit': False, 'delete': False}}, 
{'control_section_permission': {'view': False, 'create': False, 'edit': False, 'delete': False}}, 
{'account_general_permission': {'view': True, 'create': True, 'edit': True, 'delete': False}}, 
{'control_section_permission': {'view': False, 'create': False, 'edit': False, 'delete': False}}]

counters = {list(dicto.keys())[0]: Counter() for dicto in data} 

for element in data:
    for key, data in element.items():
        counters[key].update(data)


result = [{key: {item: bool(val) for item, val in data.items()}} for key, data in counters.items()]
result

output:

{'account_general_permission': {'view': True, 'create': True, 'edit': True, 'delete': False}}, 
{'control_section_permission': {'view': False, 'create': False, 'edit': False, 'delete': False}}

Upvotes: 1

BeRT2me
BeRT2me

Reputation: 13242

data = [{'account_general_permission': {'view': False, 'create': False, 'edit': False, 'delete': False}}, 
        {'control_section_permission': {'view': False, 'create': False, 'edit': False, 'delete': False}}, 
        {'account_general_permission': {'view': True, 'create': True, 'edit': True, 'delete': False}}, 
        {'control_section_permission': {'view': False, 'create': False, 'edit': False, 'delete': False}}]

# Let's make a new dictionary~
new_data = {}
for x in data:
    # Each "dictionary" in your list appears to just be a single key/value pair.
    key, values = tuple(x.items())[0]
    # If we've added this key to the dictionary already...
    if key in new_data:
        for k in values:
            # If that inner key isn't already True, or doesn't exist.
            if not new_data[key].get(k):
                # Overwrite this inner key's value OR add the new key:value pair.
                new_data[key][k] |= values[k]
    # Otherwise we can just add this dict to the new dict.
    else:
        new_data |= x

print(new_data)

# Output:

{'account_general_permission': {'view': True, 'create': True, 'edit': True, 'delete': False}, 
 'control_section_permission': {'view': False, 'create': False, 'edit': False, 'delete': False}}

An overkill, but one-line approached using pandas could look like:

data = (pd.concat([pd.DataFrame.from_dict(x, 'index') for x in data])
          .groupby(level=0)
          .max()
          .to_dict('index'))

print(data)

# Output

{'account_general_permission': {'view': True, 'create': True, 'edit': True, 'delete': False}, 
 'control_section_permission': {'view': False, 'create': False, 'edit': False, 'delete': False}}

Upvotes: 0

tdelaney
tdelaney

Reputation: 77347

I don't know about the numpy stuff, but your example applied to the original list is already reasonable, especially considering that this is a small data set. Iteration is faster than indexing and a dictionary's update function that is written in C is a bit faster still. So, using a defaultdict to create items as needed, you could do

import collections

data = [{'account_general_permission': {'view': False, 'create': False, 'edit': False, 'delete': False}}, 
{'control_section_permission': {'view': False, 'create': False, 'edit': False, 'delete': False}},
{'account_general_permission': {'view': True, 'create': True, 'edit': True, 'delete': False}},
{'control_section_permission': {'view': False, 'create': False, 'edit': False, 'delete': False}}]

resolved = collections.defaultdict(lambda: {'view': False, 'create': False,
    'edit': False, 'delete': False})

for perms in data:
    for perm_name, perm_vals in perms.items():
        resolved[perm_name].update((n,v) for n,v in perm_vals.items() if v)

for k,v in resolved.items():
    print(f"{k}: {v}")

This solution does not update the dictionaries in the original table.

Upvotes: 0

Nikolaj Š.
Nikolaj Š.

Reputation: 1986

lod = [{'account_general_permission': {'view': False, 'create': False, 'edit':
                                       False, 'delete': False}},
       {'control_section_permission': {'view': False, 'create': False, 'edit':
                                       False, 'delete': False}},
       {'account_general_permission': {'view': True, 'create': True, 'edit':
                                       True, 'delete': False}},
       {'control_section_permission': {'view': False, 'create': False, 'edit':
                                       False, 'delete': False}}]

result = {}
for d in lod:
    perm_name, perms = next(iter(d.items()))
    result[perm_name] = {perm_elem: any([perm_val,
                                         result.get(perm_name, {}
                                                    ).get(perm_elem, False)])
                         for perm_elem, perm_val in perms.items()}
{'account_general_permission': {'create': True,
                                'delete': False,
                                'edit': True,
                                'view': True},
 'control_section_permission': {'create': False,
                                'delete': False,
                                'edit': False,
                                'view': False}}

The result is a dict of dicts, though, but I feel like it's a more natural data structure for this task.

Upvotes: 0

Related Questions