关于python:字典集的所有组合到K N大小的组

All combinations of set of dictionaries into K N-sized groups

我认为这很简单,但不幸的是,事实并非如此。

我正在尝试构建一个函数来获取一个可检索的字典(即唯一字典列表),并返回一个字典的唯一分组列表。

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

CMSDK的这个问题和答案集是我能找到的最接近解决方案的东西。在将它从处理字母串到字典的过程中,我发现我的python技能不足。

我正在调整的原始功能来自第二个答案:

1
2
3
4
5
6
7
8
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'), ...

我目前的适应(由于调用map(len, i)而完全失败,错误为TypeError: object of type 'generator' has no len()):

1
2
3
4
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))) )

有一点背景:我正试图以程序化的方式将一组球员分成小组,根据他们的技能进行圣诞琐事。字典列表是由一个类似于

1
2
3
4
5
6
7
8
- name: Patricia
  skill: 4
- name: Christopher
  skill: 6
- name: Nicholas
  skill: 7
- name: Bianca
  skill: 4

yaml.load生成字典列表后:

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

所以我希望输出看起来像一个列表(其中k = 2n = 2):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
(
    # 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),但这并不能实现跨组的唯一性(即,我在一个组中的不同团队中获得相同的玩家)。

提前谢谢,圣诞快乐!

更新:

经过大量的修改,我已经适应了这一点:

这个不行

1
2
3
4
5
6
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)])))

我有个虫子

1
2
3
4
5
6
7
8
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中,我获取了这个示例输出:

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

这显然是不可取的,并制定了以下测试:

1
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-唯一字典列表的总字典来测试唯一字典,但答案是

1
2
3
4
5
6
>>> 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)中会得到什么回报。我要做更多的调查。


在这里,我将使用集合来利用新的数据类。您可以通过在decorator中设置frozen=True使数据类可哈希。首先,你要把你的玩家加入一个集合,以获得独特的玩家。然后你会得到n个大小的球队的所有球员组合。然后您可以创建一组独特的团队。然后创建有效的分组,而没有一个玩家在团队中被多次代表。最后,您可以计算整个分组中团队技能总水平的最大差异(再次利用组合),并使用该差异对有效分组进行排序。就像这样。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
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)

示例输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
(
    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)
    }))
)


口述列表并不是一个很好的数据结构,它可以映射出你真正想要重新排列的内容、玩家的名字、他们各自的属性、技能等级。您应该先将dict列表转换为name-to-skill-mapping dict:

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

1
2
3
4
5
6
7
8
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))返回:

1
2
3
4
5
6
[[('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函数获得总技能评定中方差最小的组合,该函数的关键函数返回总技能评定最高的团队和最低的团队之间的技能差异,这只需要0(n)的时间复杂性:

1
2
3
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)返回:

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