There is already a multi key dict in python and also a multivalued dict. I needed a python dictionary which is both:
example:
# probabilistically fetch a
The OP wants as follows,
d["red", "blue"] ={
"baloon": haseither('red','green',0.8),
"toy": hasonly("blue",0.15),
"car": default(0.05)
}
but this is data with embeded logic. It's very tedious to define a function for every value. What I suggest is to seprate the data and logic.
Python has a data type for this, that's class
. A callable instance of a class
can be assigned to the dict
and let the dict
pass the keys and call the object to return the result.
I've inherited and extended multiple_key_dict
to support multi-key fetch and to pass keys to the object and call the object which has been stored in the dict.
I assume data is recalculated per rule. This is Rule
class, it has list of rules. A rule is a Python expressions and it has access to len
function and keys
list. So one can write a rule like len(keys) == 1 and 'blue' in keys
.
class Rule(object):
def __init__(self, rule, data):
self.rule = rule
self.data = data
This is Data
class which has both set of data and rules.
class Data(object):
def __init__(self, rules):
self.rules= rules
def make_choice(self, data):
data = tuple(self.make_list_of_values(data))
return random.choice(data)
def make_list_of_values(self, data):
for val, weight in data:
percent = int(weight * 100)
for v in [val] * percent:
yield v
def __call__(self, keys):
for rule in self.rules:
if eval(rule.rule,dict(keys=keys)):
return self.make_choice(rule.data)
This is RuleDict
, but non-callables can not be fetched.
class RuleDict(multi_key_dict):
def __init__(self, *args, **kwargs):
multi_key_dict.__init__(self, *args, **kwargs)
def __getitem__(self, keys):
if isinstance(keys, str):
keys = (keys, )
keys_set = frozenset(keys)
for key in self.keys():
key = frozenset(key)
if keys_set <= key:
return multi_key_dict.__getitem__(self,keys[0])(keys)
raise KeyError(keys)
usage example,
d = RuleDict()
rule1 = Rule('"red" in keys and "green" in keys',(('baloon',0.8), ('car',0.05), ('toy',0.15)))
rule2 = Rule('len(keys) ==1 and "blue" in keys',(('baloon',0.25), ('car',0.35), ('toy',0.15)))
data = Data((rule1, rule2))
d['red','blue','green'] = data
print(d['red','green'])
d['red','green']
calls the object, with keys, that was assigned and return the result.
Another approach is, to make the dict
callable. This one seems a sound approach, because data and logic are separate. By this, you pass the keys and the logic, a callable, to the dict and return the result. f.e.,
def f(keys, data):
pass # do the logic and return data
d['red','blue','green'] = ('baloon', 'car', 'toy')
Now call the dict
d(('red','blue'),f)
This is callable dict
. If no callable is given, just returns the whole data.
class callable_mkd(multi_key_dict):
def __init__(self, *args, **kwargs):
multi_key_dict.__init__(self, *args, **kwargs)
def __call__(self, keys, process=None):
keys_set = frozenset(keys)
for key in self.keys():
key = frozenset(key)
if keys_set <= key:
if process:
return process(keys, self[keys[0]])
return self[keys[0]]
raise KeyError(keys)