字典集合到K个N个大小组的所有组合

时间:2018-12-24 17:32:36

标签: python algorithm dictionary combinations itertools

我虽然很简单,但事实并非如此。

我正在尝试构建一个函数以获取可迭代的字典(即,唯一字典列表)并返回列表的唯一字典组合列表。

如果我有x名球员,我想组成k个大小的n个球队。

CMSDK中的

This问题和答案是我所能找到的最接近解决方案的东西。从处理字母字符串到词典的过程中,我发现自己的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 = 2n = 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

)

每个团队分配分组都需要在各个团队中拥有唯一的玩家(即,一个团队分配分组中的多个团队中不能有同一位玩家),并且每个团队分配分组都必须是唯一的。

一旦我有了团队分配组合的列表,我将总结每个组中的技能,计算最高技能和最低技能之间的差异,然后选择最高技能和最低技能之间差异最小的分组(有差异)

我承认我不完全理解此代码。我了解第一个任务是创建一个字符串中字母的所有组合的列表,并理解return语句以在产品在不同组中不包含相同字母的情况下查找产品。

我最初的尝试是简单地使用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 ...}不能做到我认为的目的;要么就是我要解开错误的值!我正在尝试根据Flatten list of listsPython - List of unique dictionaries对总字典进行测试,但答案给出了

>>> 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 :(得分:3)

在这里,我将新数据集与集合一起使用。您可以通过在装饰器中设置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)
    }))
)

答案 1 :(得分:2)

词典列表不是一个很好的数据结构,无法将您实际想要重新排列的内容(球员姓名)映射到他们各自的属性(技能等级)。您应该先将字典列表转换为从名称到技能的映射字典:

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