Multikey Многозначный не детерминированный словарь python

В python уже существует multi key dict, а также многозначный dict. Мне нужен был словарь python, который является следующим:

Пример:

# probabilistically fetch any one of baloon, toy or car
d['red','blue','green']== "baloon" or "car" or "toy"  

Вероятность d ['red'] == d ['green'] высокая и вероятность d ['red']!= d ['red'] низкая, но возможная

одно выходное значение должно быть вероятностно определено (нечеткое) на основе правила из ключей например: в вышеприведенном случае правило может быть, если ключи имеют как "красный", так и "синий", а затем возвращают "баллон" 80% времени, если только синий, а затем возвращают "игрушку" 15% времени "автомобиль" 5% времени.

Метод setitem должен быть сконструирован таким образом, чтобы было возможно следующее:

d["red", "blue"] =[
    ("baloon",haseither('red','green'),0.8),
    ("toy",.....)
    ,....
]

Выше задается несколько значений словаря с помощью функции предиката и соответствующей вероятности. И вместо списка назначений выше даже словарь как назначение будет предпочтительнее:

d["red", "blue"] ={ 
    "baloon": haseither('red','green',0.8),
    "toy": hasonly("blue",0.15),
    "car": default(0.05)
}

В приведенном выше шаре будет возвращено 80% времени, если присутствует "красный" или зеленый , возвратите игрушку 15% времени если голубая настоящая и возвратите автомобиль 5% времени без любого условия.

Существуют ли существующие структуры данных, которые уже удовлетворяют вышеуказанным требованиям в python? если нет, то как можно изменить код мультикиддика для удовлетворения вышеуказанных требований в python?

если используется словарь, тогда может быть файл конфигурации или использовать соответствующие вложенные декораторы, которые конфигурируют приведенные выше вероятностные логики предикатов без необходимости жесткого кода, если выражения \else.

Примечание. Выше - полезный автомат для приложения автоответчика, основанного на правилах, поэтому дайте мне знать, доступна ли какая-либо подобная структура на основе правил на питоне, даже если она не использует структуру словаря?

Ответ 1

Имитированный многоязычный словарь

multi_key_dict не разрешал __getitem__() с несколькими ключами в onces...

(например, d["red", "green"])

Мульти-ключ можно моделировать с помощью tuple или set. Если порядок не имеет значения, set кажется лучшим (на самом деле хешируемый frozen set, так что [ "red", "blue"] совпадает с a ["blue", "red"].

Имитированный словарь MultiVal

Многозначные значения присущи, используя определенные типы данных, это может быть любой элемент хранения, который может быть удобно проиндексирован. Стандартное dict должно предоставить это.

недетерминизм

Используя распределение вероятности, определяемое правилами и предположениями 1 недетерминированный выбор выполняется с помощью этот рецепт из документов python.

MultiKeyMultiValNonDeterministicDict Класс

Какое имя.  !\О/ -Ницца

Этот класс принимает несколько ключей, которые определяют вероятностное множество правил из нескольких значений. При создании элемента (__setitem__()) все значения вероятности предварительно вычисляются для всех комбинаций клавиш 1. При доступе к элементу (__getitem__()) выбирается предварительно вычисленное распределение вероятности, и результат оценивается на основе случайного взвешенного выбора.

Определение

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 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

Использование

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}
}

Тестирование

Проверьте вероятности

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%

Правила соответствия вероятностей!


Сноски

  • Предпосылка распределения

    Поскольку набор правил не полностью определен, сделаны предположения о распределении вероятностей, большая часть этого делается в multi_val_rule_prob(). В принципе, любая вероятность undefined будет распределена равномерно по оставшимся значениям. Это делается для всех комбинаций клавиш и создает обобщенный ключ для случайного взвешенного выбора.

    Учитывая примерный набор правил

    d["red","blue","green"] = {
        # {rule_set} : {result: probability}
        frozenset(["red", "green"]): {"ballon": 0.8},
        frozenset(["blue"]): {"toy": 0.15},
        frozenset([]): {"car": 0.05}
    }
    

    это создаст следующие дистрибутивы

    '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)]
    

    Если это неверно, сообщите нам.

Ответ 2

одно выходное значение должно быть вероятностно определено (нечеткое) на основе правила из ключей, например: в вышеприведенном случае правило может быть, если ключи имеют как "красный", так и "синий", а затем возвращают "баллон" 80% времени, если только синий, а затем вернуть "игрушку" 15% времени "автомобиль" 5% времени.

Упомяните, что анализ вашего дела не является полным, и он неоднозначен, но вы можете сделать следующее "по духу" (сглаживание желаемых результатов):

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

Я бы сохранил это как функцию, потому что это функция! Но если вы настаиваете на том, чтобы сделать его dict-like, тогда python позволит вам сделать это, переопределив __getitem__ (IMO это не питонический).

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"

Из вашего разъяснения, OP хочет передать и сгенерировать:

randreturn ((haseither (красный, синий), Baloon: 0,8), ((hasonly (синий), игрушки: 0,15)), (по умолчанию(), автомобиль: 0,05)))

вы хотите сгенерировать функцию следующим образом:

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

где крит и значение по умолчанию в этом случае будут:

crit = [{"func": "haseither", "args": ("red", "blue"), "return_value": "baloon", "with_prob": 0.8}, ...]
default = "car"  # ??
my_random_return = make_random_return(crits, default)

Как я уже сказал, ваши вероятности неоднозначны/не складываются, поэтому вам, скорее всего, понадобится настроить этот...

Вы можете расширить определение класса, передав крит и значение по умолчанию при создании экземпляра:

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"

Ответ 3

Если можно изменить структуру данных, было бы проще иметь функцию, возвращающую нужные вам данные. Это будет полностью гибким и может вместить любые данные, если вам нужно их впоследствии изменить.

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'))

(вторая строка, очевидно, изменяется):

violet
blue

Ответ 4

OP хочет следующее:

d["red", "blue"] ={ 
    "baloon": haseither('red','green',0.8),
    "toy": hasonly("blue",0.15),
    "car": default(0.05)
}  

но это данные с встроенной логикой. Очень утомительно определять функцию для каждого значения. Я предлагаю выделить данные и логику.

У Python для этого есть тип данных, что class. Вызываемый экземпляр class может быть назначен dict и пусть dict передает ключи и вызывает объект, чтобы вернуть результат.

Я унаследовал и расширил multiple_key_dict, чтобы поддерживать извлечение нескольких ключей и передавать ключи объекту и вызывать объект, который был сохранен в dict.

Я предполагаю, что данные пересчитываются за правило. Это класс Rule, он имеет список правил. Правило является выражением Python и имеет доступ к функции len и keys. Поэтому можно написать правило вроде len(keys) == 1 and 'blue' in keys.

class Rule(object):

    def __init__(self, rule, data):
        self.rule = rule
        self.data = data

Это класс Data, который имеет как набор данных, так и правила.

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)

Это RuleDict, но не вызываемые вызовы не могут быть извлечены.

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)

пример использования,

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'] вызывает объект с назначенными ключами и возвращает результат.

Другой подход заключается в том, чтобы сделать dict вызываемым. Это похоже на здравый подход, поскольку данные и логика являются отдельными. Таким образом, вы передаете ключи и логику, вызываемую, в dict и возвращаете результат. f.e.,

def f(keys, data):
    pass # do the logic and return data

d['red','blue','green'] = ('baloon', 'car', 'toy')

Теперь вызовите dict

d(('red','blue'),f)

Это вызываемый dict. Если вызываемый не указан, он просто возвращает все данные.

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)