Python - Эффективное получение всех возможных комбинаций из двух словарей - PullRequest
0 голосов
/ 04 ноября 2018
ra_position_preferences = {"yoder3":["J","E","T","S","M","R","B","SS"],
                           "yoder4":["J","E","S","T","M","R","SS","B"],
                           "kratz3":["M","J","S","E","T","R","B","SS"],
                           "miller3":["S","M","J","E","T","R","B","SS"],
                           "nofloor":["SS","B","R","T","S","M","E","J"]}

applicants_floor_prefernce ={"J":["yoder3","yoder4","kratz3","miller3","nofloor"],
                             "E":["yoder3","yoder4","kratz3","miller3","nofloor"],
                             "S":["kratz3","miller3","yoder3","yoder4","nofloor"],
                             "M":["kratz3","miller3","nofloor","yoder3","yoder4"],
                             "T":["nofloor","yoder4","yoder3","kratz3","miller3",],
                             'SS':["yoder3","yoder4","kratz3","miller3","nofloor"],
                             'R':["kratz3","miller3","yoder3","yoder4","nofloor"],
                             'B':["yoder4","yoder3","kratz3","miller3","nofloor"]}

В приведенных выше словарях все значения являются настройками ключа. Так же, как в задаче сопоставления https://www.cs.princeton.edu/~wayne/kleinberg-tardos/pdf/01StableMatching.pdf. Я пытаюсь получить все возможные комбинации предпочтений без ошибки памяти. Также я помещаю каждую комбинацию в алгоритм Гейла-Шепли, чтобы получить все возможные соответствия. Мой код ниже:

def sensitivity_analysis(dic1,dic2): #gives all the possible combinations of the preferences 
    a=copy.deepcopy(dic1)
    b=copy.deepcopy(dic2)
    length_a=len(a.keys())
    length_b=len(b.keys())
    items_a=list(a.keys())
    items_b=list(b.keys())
    a_variants = [dict(zip(items_a, values)) 
                 for values in product(permutations(items_b), repeat=length_a)]
    b_variants = [dict(zip(items_b, values)) 
                 for values in product(permutations(items_a), repeat=length_b)]

    all_variants = product(a_variants, b_variants)
    contains_a=[]
    contains_b=[]
    for i,j in all_variants:
        contains_a.append(i)
        contains_b.append(j)
    return contains_a,contains_b

Из приведенного выше кода я получаю ошибку памяти. Есть ли другой способ сделать это? Мое предложение состоит в том, чтобы получить одну комбинацию за раз и подключить ее к функции Гейл-Шепли и получить соответствие. Затем добавьте соответствие в словаре. Если новое совпадение совпадает с последним, мы можем удалить новое совпадение, чтобы сохранить память в массиве. Но это все равно будет 278 миллионов расчетов. Ребята, у вас есть какой-нибудь эффективный способ сделать это, чтобы я мог запустить его на своем компьютере с 16 ГБ ОЗУ?

1 Ответ

0 голосов
/ 06 ноября 2018

С учетом

import itertools as it
import collections as ct


floors = ["yoder3", "yoder6", "kratz3", "miller3", "nofloor"]
applicants = ["SS", "M", "J", "T", "R", "B", "E", "S"]

preferences = {
    "J": ["yoder3", "yoder6", "kratz3", "miller3", "nofloor"],
    "E": ["yoder3", "yoder6", "kratz3", "miller3", "nofloor"],
    "S": ["kratz3", "miller3", "yoder3", "yoder6", "nofloor"],
    "M": ["kratz3", "miller3", "nofloor", "yoder3", "yoder6"],
    "T": ["nofloor", "yoder6", "yoder3", "kratz3", "miller3"],
    "SS": ["yoder3", "yoder6", "kratz3", "miller3", "nofloor"], 
    "R": ["kratz3", "miller3", "yoder3", "yoder6", "nofloor"],
    "B": ["yoder6", "yoder3", "kratz3", "miller3", "nofloor"]
}

Вспомогательная функция

def fill_missing(iterable, fillvalues):
    """Yield combinations replacing empty strings in iterable with fillvalues."""
    iter_fillvalues = map(iter, fillvalues)
    for x in iter_fillvalues:
        comb = []
        for item in iterable:
            if item == "":
                comb.append(next(x))
            else:
                comb.append(item)
        yield comb

Код

def ranked_preferences(floors, preferences):
    """Return a dict of preferences."""
    ranked = ct.defaultdict(list)
    for floor in floors:
        for i in range(len(floors)):
            idx_prefs = [
                name for name, lst in preferences.items() 
                     for j, v in enumerate(lst) if (j == i and v == floor)
            ]
            if not idx_prefs:
                idx_prefs = [""]
            ranked[floor].append(idx_prefs)
    return dict(ranked)


def preferred_assignments(ranked, applicants, top=None):
    """Yield combinations of preferred assignments."""
    if top is None:
        top = len(ranked)

    applicants = set(applicants)
    ranks = zip(ranked.values())
    for i, rank in enumerate(ranks):
        if i >= top:
            continue
        for a in it.product(*rank[0]):
            missing = a.count("")
            b = applicants - set(a)
            c = list(it.combinations(b, missing))
            d = list(it.chain.from_iterable([list(it.permutations(x, missing)) for x in c]))
            e = list(fill_missing(a, d))
            yield tuple(tuple(zip(*x)) for x in zip(it.repeat(list(ranked.keys())), e))

Демонстрация

Вывод всех комбинаций в зависимости от предпочтений:

ranked = ranked_preferences(floors, preferences)
combos = set(it.chain.from_iterable(preferred_assignments(ranked, applicants)))
print("Number of preferred combinations:", len(combos))
# Number of preferred combinations: 668

Укажите top предпочтительные выборы:

combos = set(it.chain.from_iterable(preferred_assignments(ranked, applicants, top=1)))
print("Number of preferred combinations:", len(combos))
combos

# Number of preferred combinations: 36
# {(('yoder3', 'E'),
#   ('yoder6', 'B'),
#   ('kratz3', 'R'),
#   ('miller3', 'M'),
#   ('nofloor', 'J')),
#  (('yoder3', 'E'),
#   ('yoder6', 'B'),
#   ('kratz3', 'R'),
#   ('miller3', 'M'),
#   ('nofloor', 'S')),
#  (('yoder3', 'E'),
#  ...)}

Здесь приведены только комбинации из вариантов № 1. Вы можете выбрать 2 лучших варианта, установив top=2.


Детали

Наивный подход (без предпочтений) с использованием itertools grouper Рецепт:

def all_assignments(chunks):
    """Yield all combinations of floors assigned to applicants."""
    combs = it.product(*chunks)
    for comb in combs:
        names = {c[1] for c in comb}
        if len(names) != len(floors):
            continue
        yield comb

chunks_by_floor = list(grouper(len(applicants), it.product(floors, applicants)))
chunks_by_floor[:2]
result = list(all_assignments(chunks_by_floor))
print("Number of combinations:", len(result))
# Number of combinations: 6720

Таким образом, комбинации с предпочтениями являются некоторым подмножеством этих комбинаций. Чтобы выбрать это подмножество, давайте посмотрим на предпочтения на этаже, сгруппированные по лучшим вариантам 1-5:

ranked
{'yoder3': [['J', 'E', 'SS'], ['B'], ['S', 'T', 'R'], ['M'], ['']],
 'yoder6': [['B'], ['J', 'E', 'T', 'SS'], [''], ['S', 'R'], ['M']],
 'kratz3': [['S', 'M', 'R'], [''], ['J', 'E', 'SS', 'B'], ['T'], ['']],
 'miller3': [[''], ['S', 'M', 'R'], [''], ['J', 'E', 'SS', 'B'], ['T']],
 'nofloor': [['T'], [''], ['M'], [''], ['J', 'E', 'S', 'SS', 'R', 'B']]}

Лучшие варианты на этаже упорядочены слева направо, например Индекс 0 каждого значения в dict указывает кандидатов, которые выбирают этот этаж в качестве предпочтения номер 1. Индекс 2 указывает их предпочтение номер 2 и т. Д. На некоторых этажах есть кандидаты с привязанными предпочтениями (yoder3 и kartz3, по индексу [0]). На некоторых этажах нет предпочтений (miller3 на [0]). Остальная часть логики в preferred_assignments(), то есть переменные a-e, создают комбинации претендентов на основе предпочтений (представьте вертикальные столбцы). Пропущенные значения случайным образом подставляются из оставшегося числа кандидатов.

В демонстрационной версии, поскольку эти комбинации сгруппированы по предпочтениям, мы выравниваем комбинации с помощью itertools.chain.from_iterable() и приводим к набору для удаления любых дубликатов.

...