Diccionario python Multivalue no determinista Multikey
dictionary data-structures (4)
Ya hay un dict multi-clave en python y también un dict multivaluado. Necesitaba un diccionario de Python que sea ambos:
ejemplo:
# probabilistically fetch any one of baloon, toy or car
d[''red'',''blue'',''green'']== "baloon" or "car" or "toy"
La probabilidad de d [''rojo''] == d [''verde''] es alta y la probabilidad de d [''rojo'']! = D [''rojo''] es baja pero posible
el único valor de salida debe determinarse probabilísticamente (borroso) basado en una regla de teclas, por ejemplo: en el caso anterior la regla podría ser si las teclas tienen "rojo" y "azul", entonces devuelve "globo" 80% de tiempo si solo es azul y luego regresa "juguete" 15% de tiempo "automóvil" el 5% de tiempo.
El método setitem debe diseñarse de tal manera que el siguiente sea posible:
d["red", "blue"] =[
("baloon",haseither(''red'',''green''),0.8),
("toy",.....)
,....
]
Arriba asigna múltiples valores al diccionario con una función de predicado y la probabilidad correspondiente. Y en lugar de la lista de asignaciones anterior, incluso un diccionario como asignación sería preferible:
d["red", "blue"] ={
"baloon": haseither(''red'',''green'',0.8),
"toy": hasonly("blue",0.15),
"car": default(0.05)
}
En el globo de arriba se devolverá el 80% del tiempo si está presente "rojo" o verde, devuelva el juguete 15% de tiempo si está presente en azul y devuelva el vehículo el 5% de tiempo sin ninguna condición.
¿Hay alguna estructura de datos existente que ya satisfaga los requisitos anteriores en python? si no, ¿cómo se puede modificar el código multikeydict para cumplir con los requisitos anteriores en python?
si se utiliza el diccionario, puede haber un archivo de configuración o el uso de decoradores anidados apropiados que configure las lógicas de predicados probabilísticas anteriores sin tener que codificar las sentencias if / else.
Nota: Above es un autómata útil para una aplicación de respuesta automática basada en reglas; por lo tanto, ¿me permite saber si hay un marco similar basado en reglas disponible en python, incluso si no utiliza la estructura del diccionario?
Si es posible cambiar la estructura de datos, sería más simple tener una función que devuelva los datos que necesita. Esto será completamente flexible y podría acomodar cualquier tipo de datos, en caso de que necesite cambiarlos más tarde.
import random
def myfunc(*args):
if ''red'' in args:
return ''blue''
elif ''green'' in args or ''violet'' in args:
return ''violet''
else:
r = random.random()
if 0 < r < 0.2:
return ''blue''
else:
return ''green''
print(myfunc(''green'', ''blue''))
print(myfunc(''yellow''))
salida (la segunda línea obviamente cambia):
violet
blue
el único valor de salida debe determinarse probabilísticamente (borroso) basado en una regla de teclas, por ejemplo: en el caso anterior la regla podría ser si las teclas tienen "rojo" y "azul", entonces devuelve "globo" 80% de tiempo si solo es azul y luego regresa "juguete" 15% de tiempo "automóvil" el 5% de tiempo.
Sin tener en cuenta que el análisis de su caso no está completo, y es ambiguo, pero puede hacer lo siguiente "en espíritu" (dando cuerpo a los resultados deseados):
import random
def randomly_return(*colors):
colors = set(*colors)
if ''red'' in colors and ''blue'' in colors:
if random.random() < 0.8: # 80 % of the time
return "baloon"
if ''blue'' in colors and len(colors) == 1: # only blue in colors
if random.random() < 0.15:
return "toy"
else:
if random.random() < 0.05:
return "car"
# other cases to consider
Lo mantendría como una función, ¡porque es una función! Pero si insistes en hacerlo como un dictado, entonces Python te permite hacer esto anulando __getitem__
(IMO no es pitónico).
class RandomlyReturn(object):
def __getitem__(self, *colors):
return randomly_return(*colors)
>>> r = RandomlyReturn()
>>> r["red", "blue"] # 80% of the time it''ll return "baloon"
"baloon"
A partir de su aclaración, OP quiere pasar y generar:
randreturn ((haseither (rojo, azul), baloon: 0.8), ((hasonly (azul), toy: 0.15)), (por defecto (), coche: 0.05)))
quieres generar una función de la siguiente manera:
funcs = {"haseither": lambda needles, haystack: any(n in haystack for n in needles),
"hasonly": lambda needles, haystack: len(needles) == 1 and needles[1] in haystack}
def make_random_return(crits, default):
def random_return(*colors):
colors = set(*colors)
for c in crits:
if funcs[c["func"]](c["args"], colors) and random.random() > c["with_prob"]:
return c["return_value"]
return default
return random_return
donde el crítico y el incumplimiento en este caso serían:
crit = [{"func": "haseither", "args": ("red", "blue"), "return_value": "baloon", "with_prob": 0.8}, ...]
default = "car" # ??
my_random_return = make_random_return(crits, default)
Como digo, tus probabilidades son ambiguas / no suman, así que lo más probable es que tengas que modificar esto ...
Puede extender la definición de clase pasando crítico y por defecto en instanciación:
class RandomlyReturn(object):
def __init__(self, crit, default):
self.randomly_return = make_random_return(crit, default)
def __getitem__(self, *colors):
return self.randomly_return(*colors)
>>> r = RandomlyReturn(crit, default)
>>> r["red", "blue"] # 80% of the time it''ll return "baloon"
"baloon"
El OP quiere lo siguiente,
d["red", "blue"] ={
"baloon": haseither(''red'',''green'',0.8),
"toy": hasonly("blue",0.15),
"car": default(0.05)
}
pero esto es información con lógica embebida. Es muy tedioso definir una función para cada valor. Lo que sugiero es separar los datos y la lógica.
Python tiene un tipo de datos para esto, eso es class
. Se puede asignar una instancia invocable de una class
al dict
y dejar que el dict
pase las teclas y llamar al objeto para devolver el resultado.
He heredado y extendido multiple_key_dict
para admitir la recuperación de varias teclas y para pasar claves al objeto y llamar al objeto que se ha almacenado en el dict.
Supongo que los datos se vuelven a calcular por regla. Esta es la clase de Rule
, tiene una lista de reglas. Una regla es una expresión de Python y tiene acceso a la función len
y a la lista de keys
. Entonces uno puede escribir una regla como len(keys) == 1 and ''blue'' in keys
.
class Rule(object):
def __init__(self, rule, data):
self.rule = rule
self.data = data
Esta es la clase de Data
que tiene ambos conjuntos de datos y reglas.
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)
Esto es RuleDict
, pero los no RuleDict
no se pueden recuperar.
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)
ejemplo de uso,
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'']
llama al objeto, con las teclas, que se le asignó y devuelve el resultado.
Otro enfoque es hacer que la dict
invocable. Éste parece un enfoque sensato, porque los datos y la lógica están separados. Con esto, pasas las claves y la lógica, un invocable, al dict y devuelves el resultado. fe,
def f(keys, data):
pass # do the logic and return data
d[''red'',''blue'',''green''] = (''baloon'', ''car'', ''toy'')
Ahora llama al dict
d((''red'',''blue''),f)
Este es un dict
se puede dict
. Si no se proporciona ningún llamable, simplemente devuelve la información completa.
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)
Diccionario MultiKey Simulado
multi_key_dict
no permitió __getitem__()
con múltiples claves en onces ...
(por ejemplo, d["red", "green"]
)
Se puede simular una clave múltiple con tuple
o set
claves. Si el orden no importa, set
parece el mejor (en realidad, el conjunto frozen set
cargar, de modo que [ "red", "blue"]
sea el mismo a ["blue", "red"]
.
Diccionario MultiVal Simulado
Los valores múltiples son inherentes al uso de ciertos tipos de datos, puede ser cualquier elemento de almacenamiento que pueda indexarse convenientemente. Un dict
estándar debería proporcionar eso.
No determinismo
Usando una distribución de probabilidad definida por las reglas y suposiciones 1 , la selección no determinista se realiza usando esta receta de los documentos de python.
MultiKeyMultiValNonDeterministicDict
Class
Que nombre. / o / -nice!
Esta clase toma múltiples claves que definen un conjunto de reglas probabilísticas de valores múltiples. Durante la creación del elemento ( __setitem__()
) todas las probabilidades de valor son precalculadas para todas las combinaciones de teclas 1 . Durante el acceso al elemento ( __getitem__()
) se selecciona la distribución de probabilidad precalculada y el resultado se evalúa en función de una selección ponderada aleatoria.
Definición
import random
import operator
import bisect
import itertools
# or use itertools.accumulate in python 3
def accumulate(iterable, func=operator.add):
''Return running totals''
# accumulate([1,2,3,4,5]) --> 1 3 6 10 15
# accumulate([1,2,3,4,5], operator.mul) --> 1 2 6 24 120
it = iter(iterable)
try:
total = next(it)
except StopIteration:
return
yield total
for element in it:
total = func(total, element)
yield total
class MultiKeyMultiValNonDeterministicDict(dict):
def key_combinations(self, keys):
"""get all combinations of keys"""
return [frozenset(subset) for L in range(0, len(keys)+1) for subset in itertools.combinations(keys, L)]
def multi_val_rule_prob(self, rules, rule):
"""
assign probabilities for each value,
spreading undefined result probabilities
uniformly over the leftover results not defined by rule.
"""
all_results = set([result for result_probs in rules.values() for result in result_probs])
prob = rules[rule]
leftover_prob = 1.0 - sum([x for x in prob.values()])
leftover_results = len(all_results) - len(prob)
for result in all_results:
if result not in prob:
# spread undefined prob uniformly over leftover results
prob[result] = leftover_prob/leftover_results
return prob
def multi_key_rule_prob(self, key, val):
"""
assign probability distributions for every combination of keys,
using the default for combinations not defined in rule set
"""
combo_probs = {}
for combo in self.key_combinations(key):
if combo in val:
result_probs = self.multi_val_rule_prob(val, combo).items()
else:
result_probs = self.multi_val_rule_prob(val, frozenset([])).items()
combo_probs[combo] = result_probs
return combo_probs
def weighted_random_choice(self, weighted_choices):
"""make choice from weighted distribution"""
choices, weights = zip(*weighted_choices)
cumdist = list(accumulate(weights))
return choices[bisect.bisect(cumdist, random.random() * cumdist[-1])]
def __setitem__(self, key, val):
"""
set item in dictionary,
assigns values to keys with precomputed probability distributions
"""
precompute_val_probs = self.multi_key_rule_prob(key, val)
# use to show ALL precomputed probabilities for key''s rule set
# print precompute_val_probs
dict.__setitem__(self, frozenset(key), precompute_val_probs)
def __getitem__(self, key):
"""
get item from dictionary,
randomly select value based on rule probability
"""
key = frozenset([key]) if isinstance(key, str) else frozenset(key)
val = None
weighted_val = None
if key in self.keys():
val = dict.__getitem__(self, key)
weighted_val = val[key]
else:
for k in self.keys():
if key.issubset(k):
val = dict.__getitem__(self, k)
weighted_val = val[key]
# used to show probabality for key
# print weighted_val
if weighted_val:
prob_results = self.weighted_random_choice(weighted_val)
else:
prob_results = None
return prob_results
Uso
d = MultiKeyMultiValNonDeterministicDict()
d["red","blue","green"] = {
# {rule_set} : {result: probability}
frozenset(["red", "green"]): {"ballon": 0.8},
frozenset(["blue"]): {"toy": 0.15},
frozenset([]): {"car": 0.05}
}
Pruebas
Verifica las probabilidades
N = 10000
red_green_test = {''car'':0.0, ''toy'':0.0, ''ballon'':0.0}
red_blue_test = {''car'':0.0, ''toy'':0.0, ''ballon'':0.0}
blue_test = {''car'':0.0, ''toy'':0.0, ''ballon'':0.0}
red_blue_green_test = {''car'':0.0, ''toy'':0.0, ''ballon'':0.0}
default_test = {''car'':0.0, ''toy'':0.0, ''ballon'':0.0}
for _ in xrange(N):
red_green_test[d["red","green"]] += 1.0
red_blue_test[d["red","blue"]] += 1.0
blue_test[d["blue"]] += 1.0
default_test[d["green"]] += 1.0
red_blue_green_test[d["red","blue","green"]] += 1.0
print ''red,green test ='', '' ''.join(''{0}: {1:05.2f}%''.format(key, 100.0*val/N) for key, val in red_green_test.items())
print ''red,blue test ='', '' ''.join(''{0}: {1:05.2f}%''.format(key, 100.0*val/N) for key, val in red_blue_test.items())
print ''blue test ='', '' ''.join(''{0}: {1:05.2f}%''.format(key, 100.0*val/N) for key, val in blue_test.items())
print ''default test ='', '' ''.join(''{0}: {1:05.2f}%''.format(key, 100.0*val/N) for key, val in default_test.items())
print ''red,blue,green test ='', '' ''.join(''{0}: {1:05.2f}%''.format(key, 100.0*val/N) for key, val in red_blue_green_test.items())
red,green test = car: 09.89% toy: 10.06% ballon: 80.05%
red,blue test = car: 05.30% toy: 47.71% ballon: 46.99%
blue test = car: 41.69% toy: 15.02% ballon: 43.29%
default test = car: 05.03% toy: 47.16% ballon: 47.81%
red,blue,green test = car: 04.85% toy: 49.20% ballon: 45.95%
¡Las probabilidades coinciden con las reglas!
Notas a pie de página
Asunción de distribución
Dado que el conjunto de reglas no está completamente definido, se hacen suposiciones sobre las distribuciones de probabilidad, la mayoría de esto se hace en
multi_val_rule_prob()
. Básicamente, cualquier probabilidad indefinida se distribuirá uniformemente sobre los valores restantes. Esto se hace para todas las combinaciones de teclas, y crea una interfaz de clave generalizada para la selección ponderada aleatoriamente.Dado el conjunto de reglas de ejemplo
d["red","blue","green"] = { # {rule_set} : {result: probability} frozenset(["red", "green"]): {"ballon": 0.8}, frozenset(["blue"]): {"toy": 0.15}, frozenset([]): {"car": 0.05} }
esto creará las siguientes distribuciones
''red'' = [(''car'', 0.050), (''toy'', 0.475), (''ballon'', 0.475)] ''green'' = [(''car'', 0.050), (''toy'', 0.475), (''ballon'', 0.475)] ''blue'' = [(''car'', 0.425), (''toy'', 0.150), (''ballon'', 0.425)] ''blue,red'' = [(''car'', 0.050), (''toy'', 0.475), (''ballon'', 0.475)] ''green,red'' = [(''car'', 0.098), (''toy'', 0.098), (''ballon'', 0.800)] ''blue,green'' = [(''car'', 0.050), (''toy'', 0.475), (''ballon'', 0.475)] ''blue,green,red''= [(''car'', 0.050), (''toy'', 0.475), (''ballon'', 0.475)] default = [(''car'', 0.050), (''toy'', 0.475), (''ballon'', 0.475)]
Si esto es incorrecto, por favor avise.