优化随机卡片分配

时间:2021-04-15 14:10:02

标签: python random

我有一副 21 张牌和 6 名玩家。我会用字母表中的一个字母标记每张卡片。
我必须在某些条件下随机分配给每个玩家 3 张牌(总共 18 张牌,其中 3 张在牌组中),分为两个不同的类别:

  • andlist(x) = [[g, e, h],[k, m, f]],表示:'g, e, h, k, m, f 不能分配给玩家 x'
  • orlist(x) = [[a, l, i], [j, d ,o], [b, n, c]],这意味着:'a 或 l 或 i,j 或 d 或 o,b 或 n 或 c 必须分配给玩家 x'

当然这只是一个例子; andlist(x)orlist(x) 可以为每个玩家包含不同数量的嵌套三元组。

我写了一个简单的代码,将 18 张牌完全随机地分配给 6 个玩家,直到满足条件为止(使用 while 循环),但是这种策略非常低效,因为它需要很长时间才能找到适当的分配。

在 Python 中是否有更有效的方法来处理这种情况?

1 个答案:

答案 0 :(得分:0)

这似乎是一个有趣的小吃——我相信约束求解器或其他东西会更好,但这里有一些基于 orlist 定义我们可以迭代的某些基本情况的想法,递归树可能的解决方案,直到我们找到一个可行的解决方案。 (事后想来,这实际上并没有通过可能的手牌组合的完整树进行递归,因为 generate_new_solution 本身不是递归生成器...)

当然,它也可能有问题。

import random
import string
import itertools
from functools import reduce
from operator import or_
from typing import Iterable, Dict

deck_size = 21
player_count = 6
hand_size = 3

card_names = set(string.ascii_lowercase[i] for i in range(deck_size))

forbidden_cards = {
    0: [set("geh"), set("kmf")],
}

required_sets = {
    # 'a or l or i, and j or d or o, and b or n or c must be assigned to player x'.
    0: [set("ali"), set("jdo"), set("bnc")],
    1: [set("geh")],
    2: [set("kmf")],
}

# Flatten the forbidden card dict sets for faster iteration

flat_forbidden_cards = {idx: frozenset(reduce(or_, forbidden_cards.get(idx, ()), set())) for idx in range(player_count)}


def is_solution_forbidden(solution: Dict[int, frozenset]) -> bool:
    """
    Figure out if the solution is forbidden, i.e. any player has a forbidden card
    """
    for idx, cards in solution.items():
        if cards & flat_forbidden_cards.get(idx):
            return True
    return False


def is_solution_finished(solution: Dict[int, frozenset]) -> bool:
    """
    Figure out if the solution is finished, i.e. all players have been dealt hand_size cards.
    """
    return all(len(hand) == hand_size for hand in solution.values())


def get_root_solutions() -> Iterable[Dict[int, frozenset]]:
    """
    Generate "root" solutions that satisfy the "required sets" constraints.
    """
    required_roots = {
        idx: [frozenset(card_ids) for card_ids in itertools.product(*required_sets.get(idx, ()))]
        for idx in range(player_count)
    }
    for base_sol_cards in itertools.product(*required_roots.values()):
        yield {i: cards for i, cards in enumerate(base_sol_cards)}


def iterate_solutions(base_solution: Dict[int, frozenset]) -> Iterable[Dict[int, frozenset]]:
    """
    Recursive generator for solutions based on `base_solution`.

    Only ever yields finished solutions.
    """
    new_sol = generate_new_solution(base_solution)
    if is_solution_finished(new_sol):
        yield new_sol
    else:
        yield from iterate_solutions(new_sol)


def generate_new_solution(base_solution: Dict[int, frozenset]):
    used_cards = reduce(or_, base_solution.values())
    available_cards = card_names - used_cards
    deal = {}
    for idx in range(player_count):
        if len(base_solution[idx]) < hand_size:
            allowed_player_cards = available_cards - set(deal.values()) - flat_forbidden_cards[idx]
            if not allowed_player_cards:
                raise RuntimeError("Ran out of cards to pick")
            deal[idx] = random.choice(list(allowed_player_cards))
    new_sol = base_solution.copy()
    for idx, hand in base_solution.items():
        if idx in deal:
            new_sol[idx] = frozenset(hand | {deal[idx]})
    assert not is_solution_forbidden(new_sol)
    return new_sol


for root_sol in get_root_solutions():
    assert not is_solution_forbidden(root_sol)  # Should never happen, good to check though
    for solution in iterate_solutions(root_sol):
        print({idx: "".join(sorted(hand)) for idx, hand in solution.items()})

输出是这样的(尽管实际上有 243 行,而不是 6 行),描述每个玩家手中牌的字典。

{0: 'bdi', 1: 'hmu', 2: 'efq', 3: 'apt', 4: 'cko', 5: 'jns'}
{0: 'bjl', 1: 'eno', 2: 'ikm', 3: 'hqs', 4: 'adg', 5: 'frt'}
{0: 'abd', 1: 'glm', 2: 'fjn', 3: 'ior', 4: 'hps', 5: 'equ'}
{0: 'bio', 1: 'hmp', 2: 'fjl', 3: 'aeu', 4: 'drt', 5: 'kns'}
{0: 'blo', 1: 'agh', 2: 'fru', 3: 'mnp', 4: 'des', 5: 'cqt'}
相关问题