Reputation: 299
I'm trying to split a list of dictionaries into two lists based on a conditional being the type of the value in the dictionaries. Is there anyway to do this in a one pass list comprehension?
Currently, I am doing this:
nonvals = [{k: v for k, v in act.items() if type(v) != int} for act in actual]
vals = [{k: v for k, v in act.items() if type(v) == int} for act in actual]
Is there anyway I can return two lists based on the conditional in one list comprehension? If not what's a more pythonic way to split this list of dictionaries?
I am attempting to do the following as a final solution, but I feel like there is a lot of code repetition and extraneous looping that can be avoided - for reference both actual
and expected
are lists of dictionaries. I would just like to get to the final solution in the fewest lines as possible.
I want to check if the non-int key, value pairs in expected are in the non-int key, value pairs in actual. And in actual I just want to check if all the int key, value pairs are in the [-11, 11] range.
expected = [{'time': '12:34:22', 'place': 'LA', 'person': 'Mike', 'val1': 2, 'val2': 3, 'val3': 4},
{'time': '11:45:15', 'place': 'SF', 'person': 'Emily', 'val1': 2, 'val2': 3, 'val3': 4}]
actual = [{'time': '12:34:22', 'place': 'LA', 'person': 'Mike', 'val1': 2, 'val2': 3, 'val3': 4},
{'time': '11:45:15', 'place': 'SF', 'person': 'Emily', 'val1': 2, 'val2': 3, 'val3': 4},
{'time': '21:19:57', 'place': 'LA', 'person': 'Leo', 'val1': 2, 'val2': 3, 'val3': 4},
{'time': '15:43:11', 'place': 'LA', 'person': 'Marge', 'val1': 2, 'val2': 3, 'val3': 4}]
def check(expected, actual):
nonvals = [{k: v for k, v in act.items() if type(v) != int} for act in actual]
vals = [{k: v for k, v in act.items() if type(v) == int} for act in actual]
for act in actual:
for k, v in act.items():
if v in vals and v not in range(-11, 11):
return False
for exp in expected:
if {k: v for k, v in exp.items() if type(v) != int} not in nonvals:
return False
return True
Upvotes: 1
Views: 7450
Reputation: 31329
There is no general pythonic solution that will split a list based on some condition, let alone do the even more complicated job of splitting a dictionary based on some condition (like the type of the value matching a specific type).
Doing what you're doing is fairly readable and not a bad solution, but if you run into this problem more often, you could just write a function you can then apply to the dictionaries in the list:
def pdict(d, condition):
"""
Partition a dictionary based on some condition function
:param d: a dict
:param condition: a function with parameters k, v returning a bool for k: v in d
:return: two dictionaries, with the contents of d, split according to condition
"""
return {
k: v for k, v in d.items() if condition(k, v)
}, {
k: v for k, v in d.items() if not condition(k, v)
}
original = [{'a': 1, 'b': 'two', 'c': 3}, {'a': 'one', 'b': 2}, {'a': 1, 'b': 2}]
int_result, nonint_result = [
list(t) for t in zip(*[
pdict(d, lambda k, v: isinstance(v, int)) for d in original
])
]
print(int_result)
print(nonint_result)
This is clean and allows you to simply reuse partition_dict
for similar cases.
The output produced by the example:
[{'a': 1, 'c': 3}, {'b': 2}, {'a': 1, 'b': 2}]
[{'b': 'two'}, {'a': 'one'}, {}]
A more simple example of (re)using pdict()
:
d = {1: 42, 2: 33, 3: 5, 4: 10}
odd, even = pdict(d, lambda k, v: v % 2 == 1)
print(odd, even)
Output:
{2: 33, 3: 5} {1: 42, 4: 10}
A rewrite of pdict()
that only loops once (as suggested by @blues), but is a bit wordier:
def pdict(d, condition):
"""
Partition a dictionary based on some condition function
:param d: a dict
:param condition: a function with parameters k, v returning a bool for k: v in d
:return: two dictionaries, with the contents of d, split according to condition
"""
meets_condition = {}
does_not_meet_condition = {}
for k, v in d.items():
if condition(k, v):
meets_condition[k] = v
else:
does_not_meet_condition[k] = v
return meets_condition, does_not_meet_condition
A disadvantage of this may be that for each element being added to the dictionaries, there may be some overhead in look-ups that the original example code can avoid in the dictionary comprehension. You could run performance tests to decide what is preferable, but the latter solution does avoid looping through the entire original dictionary twice. I think I'd prefer the first solution for anything that's not crazy large.
Upvotes: 2