Your code is pretty good, there is one thing that I would add,
the for
-else
keyword, as this gets rid of the found
variable.
Which honestly is just noise.
This is as if a for loop runs completely without breaking then the else
will run too. But if it break
s then it won't run the else
.
This can leave you with:
def group_by_excluding_key(list_of_dicts, key_field):
"""
Takes a list of `dict` items and groups by ALL KEYS in the dict EXCEPT the key_field.
:param list_of_dicts: List of dicts to group
:param key_field: key field in dict which should be excluded from the grouping
"""
output = []
for item in list_of_dicts:
item_key = item.pop(key_field)
for existing_group, found_keys in output:
if existing_group.viewitems() == item.viewitems():
found_keys.append(item_key)
break
else:
output.append((item, [item_key]))
return output
Other than that your code is good.
But if I were to were to write this, I'd prefer a very small solution.
Lets say dict
s are hash able, what you want is a dictionary that has the modified item
as the key, and the popped item_key
as the value.
This obviously has two down-sides, it's not ordered, and dicts aren't hash able.
Both easily solved with collections.OrderedDict
and tuple(dict.items())
.
And so can result in:
from collections import OrderedDict
def group_by_excluding_key(list_of_dicts, key_field):
"""
Takes a list of `dict` items and groups by ALL KEYS in the dict EXCEPT the key_field.
:param list_of_dicts: List of dicts to group
:param key_field: key field in dict which should be excluded from the grouping
"""
output = OrderedDict()
for item in list_of_dicts:
key = item.pop(key_field)
output.setdefault(tuple(item.items()), []).append(key)
return [(dict(key), value) for key, value in output.items()]
This has the benefit of moving the for
loop into the OrderedDict
, and possibly getting \$O(1)\$ key lookup, but requires you to change the type of all the keys, twice.