Reputation: 3277
I have the following lambda expression:
constraints = lambda values: (
values['volume'] < 10,
values['mass'] < 100,
values['nItems'] <= 10,
values['nItems'] >= 5
)
I have a list that contains brands (dynamically populated), such as
brands = ['nestle', 'gatorate']
I want to be able to put in additional expressions dynamically such that:
constraints = lambda values: (
values['volume'] < 10,
values['mass'] < 100,
values['nItems'] <= 10,
values['nItems'] >= 5,
#dynamically adds these parameters
values['nestle'] < 5,
values['gatorate'] < 5
)
How can I do that using a for loop that iterates over brands and dynamically populates additional parameters into constraints from the brands list?
The answer can be in any form provided that I can get the desired constraints function.
Upvotes: 4
Views: 2898
Reputation: 8982
First, don't use lambda where you don't need it
Always use a def statement instead of an assignment statement that binds a lambda expression directly to an identifier.
Yes:
def f(x): return 2*x
No:
f = lambda x: 2*x
The first form means that the name of the resulting function object is specifically 'f' instead of the generic ''. This is more useful for tracebacks and string representations in general. The use of the assignment statement eliminates the sole benefit a lambda expression can offer over an explicit def statement (i.e. that it can be embedded inside a larger expression)
Now to the solution. Make a list of functions that will check your values.
Updating the list will mean updating the constraints.
def _constrain_main(values):
"""Static constraints"""
return (values['volume'] < 10 and values['mass'] < 100 and
values['nItems'] <= 10 and values['nItems'] >= 5)
def constrain(values):
"""Check for all constraints"""
return all(c(values) for c in constraints)
# list of constraints
constraints = [_constrain_main]
# example
values = {
'volume': 8,
'mass': 80,
'nItems': 8,
'nestle': 6,
'gatorate': 6,
}
print(constrain(values)) # True
# now append other lambdas (lambda is desired here)
constraints.append(lambda values: values['nestle'] < 5)
constraints.append(lambda values: values['gatorate'] < 5)
print(constrain(values)) # False
Upvotes: 4
Reputation: 15909
You can replace the lambda by a proper function and get some speedups. There is nothing that prevents your lambda from being a function.
Additionally, you cannot add dynamic expressions to a function. The closest approach would be to pass the keys and thresholds as parameters to your function:
def comparevalues(values, keys, thresholds, cmpfuncs):
return [f(values[k], t) for f, k, t in zip(cmpfuncs, keys, thresholds)]
Here values
is a dictionary, keys
is a list of keys used in each "rule", threshold
is the threshold applied to each "rule" and cmpfuncs
are comparison functions used in each rule (e.g. <
, <=
, >
...), you can create your own operators if they are complex, for simple thresholds you can use python's operators:
>>> import operator as op
>>> (5 > 6) == op.gt(5, 6) # Same operation, explicit vs function call
All 3 lists keys
, thresholds
and cmpfuncs
should have the same length, as each of the elements forms a rule triplet.
As an example:
>>> import operator as op
>>> dictionary = {'a': 5, 'b': 10}
>>> keys = ['a', 'b', 'a']
>>> thresholds = [1, 4, 5]
>>> cmpfuncs = [op.gt, op.gt, op.lt]
>>> comparevalues(dictionary, keys, thresholds, cmpfuncs)
[True, True, False]
The above is creating 3 rules: (1) 'a' > 1, (2) 'b' > 4 and (3) 'a' < 5. Note that op.gt = >
and op.lt = <
.
If you want to dynamically add more operators, you just have to maintain a global lists of keys
, threshodlsand
cmpfuncs`, update them with new rules, and pass them with every function call.
Now that the approach is explained, find bellow a way of making the same problem more portable:
def comparerules(values, rules):
return [f(values[k], t) for f, k, t in rules]
The approach is the same as above, but in this case rules
is a list of triplets, where each triplet is a tuple of the form (cmpfunc, key, threshold
).
To use this function with equivalent rules to the above example:
>>> values = {'a': 5, 'b': 10}
>>> rules = [(op.gt, 'a', 1), (op.gt, 'b', 4), (op.lt, 'a', 5)]
>>> comparerules(values, rules)
[True, True, False]
This way, you only have to maintain a global rules
array, edit it to append/remove new rules, and send it in every function call:
>>> rules += [(op.gt, 'b', 10)] # values['b'] > 10
>>> comparerules(values, rules)
[True, True, False, False]
And last, to check whether values
satisfies all the conditions, just use python's all
:
>>> all(comparerules(values, rules))
False
Upvotes: 2