Все комбинации набора словарей в K N-размерных групп - PullRequest
0 голосов
/ 24 декабря 2018

Я думаю, что это было бы просто, к сожалению, это не так.

Я пытаюсь создать функцию, чтобы взять итеративный из словарей (то есть, список уникальных словарей) и вернуть список списков.уникальных групп словарей.

Если у меня есть x игроков, я хотел бы сформировать k команды n размера.

Этот вопрос иНабор ответов из CMSDK - самое близкое к решению, которое я могу найти.Приспосабливая его от обработки строк писем к словарям, я нахожу мои навыки Python неадекватными.

Исходная функция, которую я адаптирую, основана на втором ответе:

import itertools as it
def unique_group(iterable, k, n):
    """Return an iterator, comprising groups of size `k` with combinations of size `n`."""
    # Build separate combinations of `n` characters
    groups = ("".join(i) for i in it.combinations(iterable, n))    # 'AB', 'AC', 'AD', ...
    # Build unique groups of `k` by keeping the longest sets of characters
    return (i for i in it.product(groups, repeat=k) 
                if len(set("".join(i))) == sum((map(len, i))))     # ('AB', 'CD'), ('AB', 'CE'), ... 

Моя текущая адаптация (это совершенно не с ошибкой TypeError: object of type 'generator' has no len() из-за вызова map(len, i)):

def unique_group(iterable, k, n):
    groups = []
    groups.append((i for i in it.combinations(iterable, n)))
    return ( i for i in it.product(groups, repeat=k) if len(set(i)) == sum((map(len, i))) )

Для некоторого контекста: я пытаюсь программно разделить группу игроков на команды для рождественских викторинна основании их навыков.Список словарей сформирован из файла yaml, который выглядит как

- name: Patricia
  skill: 4
- name: Christopher
  skill: 6
- name: Nicholas
  skill: 7
- name: Bianca
  skill: 4

, который после yaml.load создает список словарей:

players = [{'name':'Patricia', 'skill':4},{'name':'Christopher','skill':6},
           {'name':'Nicholas','skill':7},{'name':'Bianca','skill':4}]

Так что я ожидаю, что вывод будет выглядетькак их список (где k = 2 и n = 2):

(
    # Team assignment grouping 1
    (
        # Team 1
        ( {'name': 'Patricia', 'skill': 4}, {'name': 'Christopher', 'skill': 6} ),
        # Team 2
        ( {'name': 'Nicholas', 'skill': 7}, {'name': 'Bianca', 'skill': 4} )
    ),
    # Team assignment grouping 2
    (
        # Team 1
        ( {'name': 'Patricia', 'skill': 4}, {'name': 'Bianca', 'skill': 4} ),
        # Team 2
        ( {'name': 'Nicholas', 'skill': 7}, {'name': 'Christopher', 'skill': 6} )
    ),

    ...,

    # More unique lists

)

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

Как только у меня будет список комбинаций командных назначений, я подытожу навыки в каждой группе, возьму разницу между самым высоким навыком и самым низким навыком.и выберите группировку (с дисперсией) с наименьшей разницей между наивысшими и наименьшими навыками.

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

Моя первоначальная попытка состояла в том, чтобы просто взять it.product(it.combinations(iterable, n), repeat=k), но это не позволяет достичь уникальности в разных группах (то есть я получаю одного и того же игрока в разных командах в одной группе).

Заранее спасибо, и с Рождеством!


Обновление:

После значительного беспокойства я получил адаптацию к этому:

Это не работает

def unique_group(iterable, k, n):
    groups = []
    groups.append((i for i in it.combinations(iterable, n)))
    return (i for i in it.product(groups, repeat=k)\
        if len(list({v['name']:v for v in it.chain.from_iterable(i)}.values())) ==\
        len(list([x for x in it.chain.from_iterable(i)])))

Я получаю ошибку

Traceback (most recent call last):
  File "./optimize.py", line 65, in <module>
    for grouping in unique_group(players, team_size, number_of_teams):
  File "./optimize.py", line 32, in <genexpr>
    v in it.chain.from_iterable(i)})) == len(list([x for x in
  File "./optimize.py", line 32, in <dictcomp>
    v in it.chain.from_iterable(i)})) == len(list([x for x in
TypeError: tuple indices must be integers or slices, not str

Это сбивает меня с толку и дает понять, что я не знаю, что делает мой код.В ipython я взял этот пример вывода:

assignment = (
({'name': 'Patricia', 'skill': 4}, {'name': 'Bianca', 'skill': 4}),
({'name': 'Patricia', 'skill': 4}, {'name': 'Bianca', 'skill': 4})
)

, что явно нежелательно, и сформулировал следующий тест:

len(list({v['name']:v for v in it.chain.from_iterable(assignment)})) == len([v for v in it.chain.from_iterable(assignment)])

, который правильно отвечает False.Но это не работает в моем методе.Вероятно, это потому, что я сейчас занимаюсь кодированием грузового культа.

Я понимаю, что делает it.chain.from_iterable(i) (это сводит кортеж кортежей словарей в просто кортеж словарей).Но кажется, что синтаксис {v['name']:v for v in ...} делает , а не делает то, что я думаю;либо это, либо я распаковываю неправильные значения!Я пытаюсь проверить уникальные словари по общим словарям на основе Сгладить список списков и Python - Список уникальных словарей , но ответ дает мне

>>> L=[
... {'id':1,'name':'john', 'age':34},
... {'id':1,'name':'john', 'age':34},
... {'id':2,'name':'hanna', 'age':30},
... ] 
>>> list({v['id']:v for v in L}.values())

Не так легко адаптироваться в этих обстоятельствах, как я думал, и я понимаю, что на самом деле не знаю, что возвращается в it.product(groups, repeat=k).Мне придется больше расследовать.

Ответы [ 2 ]

0 голосов
/ 25 декабря 2018

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

player_skills = {player['name']: player['skill'] for player in players}
# player_skills becomes {'Patricia': 4, 'Christopher': 6, 'Nicholas': 7, 'Blanca': 4}

, чтобы вы могли рекурсивно вычесть комбинацию n игроков из пула игроков iterable, доколичество групп достигает k:

from itertools import combinations
def unique_group(iterable, k, n, groups=0):
    if groups == k:
        yield []
    pool = set(iterable)
    for combination in combinations(pool, n):
        for rest in unique_group(pool.difference(combination), k, n, groups + 1):
            yield [combination, *rest]

При вводе пробы list(unique_group(player_skills, 2, 2)) возвращает:

[[('Blanca', 'Christopher'), ('Nicholas', 'Patricia')],
 [('Blanca', 'Nicholas'), ('Christopher', 'Patricia')],
 [('Blanca', 'Patricia'), ('Christopher', 'Nicholas')],
 [('Christopher', 'Nicholas'), ('Blanca', 'Patricia')],
 [('Christopher', 'Patricia'), ('Blanca', 'Nicholas')],
 [('Nicholas', 'Patricia'), ('Blanca', 'Christopher')]]

Вы можете получить комбинацию с наименьшей дисперсией в общем рейтинге навыков.с помощью функции min с ключевой функцией, которая возвращает разницу в умениях между командой с наивысшим общим рейтингом умений и командой с наименьшим, что занимает всего O (n) по временной сложности:

def variance(groups):
    total_skills = [sum(player_skills[player] for player in group) for group in groups]
    return max(total_skills) - min(total_skills)

так что min(unique_group(player_skills, 2, 2), key=variance) возвращает:

[('Blanca', 'Nicholas'), ('Christopher', 'Patricia')]
0 голосов
/ 24 декабря 2018

Здесь я бы использовал новые классы данных с наборами.Вы можете сделать класс данных хэшируемым, установив frozen=True в декораторе.Сначала вы добавите своих игроков в набор, чтобы получить уникальных игроков.Тогда вы получите все комбинации игроков для команд n размера.Тогда вы могли бы создать набор уникальных команд.Затем создайте действительные группировки, в то время как ни один игрок не представлен более чем один раз в командах.Наконец, вы можете рассчитать максимальное несоответствие в общем уровне навыков команды по группировке (снова используя комбинации) и использовать это для сортировки ваших действительных группировок.Ну как то так.

from dataclasses import dataclass
from itertools import combinations
from typing import FrozenSet

import yaml


@dataclass(order=True, frozen=True)
class Player:
    name: str
    skill: int


@dataclass(order=True, frozen=True)
class Team:
    members: FrozenSet[Player]

    def total_skill(self):
        return sum(p.skill for p in self.members)


def is_valid(grouping):
    players = set()
    for team in grouping:
        for player in team.members:
            if player in players:
                return False
            players.add(player)
    return True


def max_team_disparity(grouping):
    return max(
        abs(t1.total_skill() - t2.total_skill())
        for t1, t2 in combinations(grouping, 2)
    )


def best_team_matchups(player_file, k, n):
    with open(player_file) as f:
        players = set(Player(p['name'], p['skill']) for p in yaml.load(f))
    player_combs = combinations(players, n)
    unique_teams = set(Team(frozenset(team)) for team in player_combs)
    valid_groupings = set(g for g in combinations(unique_teams, k) if is_valid(g))
    for g in sorted(valid_groupings, key=max_team_disparity):
        print(g)


best_team_matchups('test.yaml', k=2, n=4)

Пример вывода:

(
    Team(members=frozenset({
        Player(name='Chr', skill=6),
        Player(name='Christopher', skill=6),
        Player(name='Nicholas', skill=7),
        Player(name='Patricia', skill=4)
    })),
    Team(members=frozenset({
        Player(name='Bia', skill=4),
        Player(name='Bianca', skill=4),
        Player(name='Danny', skill=8),
        Player(name='Nicho', skill=7)
    }))
)
...