Reputation: 7653
x = {'a': (2, 3), 'b': (4, 1), 'c': (3, 2), 'd': (1, 0), 'e': (2, 0)}
x = {
k: v
for k, v in x.items()
if (v[0] >= 2 and v[1] >= 0) or (v[0] >= 1 and v[1] > 1)
}
print(x)
In the 'if' block, I have a lot of logical 'or' enclosed with parenthesis. Is there better way to express it in dict comprehension?
Also, I can omit the () in the 'if' statement to make it more consise. Right?
Upvotes: 2
Views: 101
Reputation: 4975
I think the question is a bit bad-posed since expect to deal with an uncountable many or
s is independent of the comprehension-form.
My suggestion is to use a systematic approach to deal with a chain of (irreducible) or
conjunctions based on an auxiliary function.
General idea: form two groups wrt the index of the values of the dictionary, i.e. v0
, v1
, together with an own collection of unitary comparison operators ( for short UCO). Then evaluate each group with the proper UCO, apply a termwise and
to the "product" of both groups and finally reduce with an any
.
The key step is hence the construction of the unitary comparison operators UCO. It depends on methodcaller
.
Notice that are some implicit data which are very important because are invariant of the iterations: the values that you use to make the comparison. These will be classified with dict
as following:
lb
: lower boundlbe
: lower bound also equal to itub
: upper boundube
: upper bound also equal to itneq
: not equal toeq
: equal to
and used with a mapper which assigns the corresponding name name of the method.
It is not mandatory but could avoid confusions with the order operators, here an example:lb = 4
2 > lb # binary inequality greater than
int(lb).__lt__(2) # unitary inequality (UCO) less than
To deal with the invariance of boundaries I used a decorator (not mandatory).
Here my solution:
from operator import methodcaller, __and__
def systematic_comparison(v0_boundaries, v1_boundaries):
# v0_boundaries, v1_boundaries: both tuple of dictionaries, ({2: 'lbe'}, {1: 'lbe'})
def __systematic_comparison(v0, v1):
# mapper boundary type - order relation
mapper = {'lb': '__gt__', 'lbe': '__ge__', 'ub': '__lt__', 'ube': '__le__', 'ne': '__ne__', 'eq': '__eq__'}
# make unitary comparison operators
v0_comparisons = []
for d in v0_boundaries:
k, v = tuple(d.items())[0]
v0_comparisons.append(methodcaller(mapper[v], k))
v1_comparisons = []
for d in v1_boundaries:
k, v = tuple(d.items())[0]
v1_comparisons.append(methodcaller(mapper[v], k))
# evaluate unitary comparisons operator
v0_checks = [comp(v0) for comp in v0_comparisons]
v1_checks = [comp(v1) for comp in v1_comparisons]
# pairwise-and separated by or
return any(map(__and__, v0_checks, v1_checks))
return __systematic_comparison
x = {'a': (2, 3), 'b': (4, 1), 'c': (3, 2), 'd': (1, 0), 'e': (2, 0)}
x = {
k: (v0, v1)
for k, (v0, v1) in x.items()
if systematic_comparison(v0_boundaries=({2: 'lbe'}, {1: 'lbe'}), v1_boundaries=({0: 'lbe'}, {1: 'lb'}))(v0, v1)
}
print(x)
#{'a': (2, 3), 'b': (4, 1), 'c': (3, 2), 'e': (2, 0)}
The code can be made a bit more compact like this:
def systematic_comparison(v0_boundaries, v1_boundaries):
# v0_boundaries, v1_boundaries: both tuple of dictionaries, ({2: 'lbe'}, {1: 'lbe'})
def __systematic_comparison(v0, v1):
# mapper boundary type - order relation
mapper = {'lb': '__gt__', 'lbe': '__ge__', 'ub': '__lt__', 'ube': '__le__', 'ne': '__ne__', 'eq': '__eq__'}
# make unitary comparison operators
v_comparisons = []
for d in v0_boundaries + v1_boundaries: # merge together -> size double
k, v = tuple(d.items())[0]
v_comparisons.append(methodcaller(mapper[v], k))
# size of half list of v_comparisons
n = len(v0_boundaries)
# groupwise comparison and pairwise-and separated by or
return any(map(__and__, [comp(v0) for comp in v_comparisons[:n]], [comp(v1) for comp in v_comparisons[n:]]))
return __systematic_comparison
Here an example of the syntax with 3 or more conditions:
v0_boundaries = ({2: 'lbe'}, {1: 'lbe'}, {8: 'ne'}, ...)
v1_boundaries = ({0: 'lbe'}, {1: 'lb'}, {10: 'eq'}, ...)
systematic_comparison(v0_boundaries, v1_boundaries)
Upvotes: 0
Reputation: 1859
One option is to write a function that checks for whether or not you want to include the item:
def my_filter(tup):
if tup[0] >= 2 and tup[1] >= 0:
return True
else: #Chain whatever you want as elif
return tup[0] >= 1 and tup[1] > 1
x = {'a': (2, 3), 'b': (4, 1), 'c': (3, 2), 'd': (1, 0), 'e': (2, 0)}
y = {
k: v
for k, v in x.items()
if my_filter(v)
}
print(y)
Or you could wrap both conditions with any
:
z = {
k: v
for k, v in x.items()
if any(
[v[0] >= 2 and v[1] >= 0,
v[0] >= 1 and v[1] > 1]
)
}
print(z)
Both of these will output:
{'a': (2, 3), 'b': (4, 1), 'c': (3, 2), 'e': (2, 0)}
Upvotes: 0
Reputation: 19242
You can unpack the tuple. Keep the parentheses, even though you can technically get away with it; and
has a higher precedence than or
, but not everyone knows that and this makes it more readable:
x = {k: (fst, snd) for k, (fst, snd) in x.items() if (fst >= 2 and snd >= 0) or (fst >= 1 and snd > 1)}
This outputs:
{'a': (2, 3), 'b': (4, 1), 'c': (3, 2), 'e': (2, 0)}
Upvotes: 3
Reputation: 36352
In the 'if' block, I have a lot of logical 'or' enclosed with parenthesis. Is there better way to express it in dict comprehension?
No. Your both "main" conditions have one constraint that is a superset of the same constraint of the other side, and one that is incompatible.
Three boolean operators is the minimum you can do.
Upvotes: 1