【问题标题】:Partition set with constraints (backtracking with Python)带约束的分区集(使用 Python 回溯)
【发布时间】:2023-11-16 02:49:01
【问题描述】:

我有一组 N 个项目,我想将它们分成 K 个大小为 n1、n2、...、nk(其中 n1 + n2 + ... + nk = N)

我还限制了哪些项目可以属于哪个子集。

对于我的问题,至少存在一种解决方案。

我希望在 Python 中实现一种算法来生成(至少)一个解决方案。

示例:

可能性:

Item\Subset 0 1 2
A True True False
B True True True
C False False True
D True True True
E True False False
F True True True
G False False True
H True True True
I True True False

尺寸限制:(3, 3, 3)

可能的解决方案:[0, 0, 2, 1, 0, 1, 2, 2, 1]

实施:

到目前为止,我已经成功地尝试了暴力破解,但我现在想找到一个更优化的算法。

我在考虑回溯,但我不确定这是正确的方法,也不确定我的实现是否正确:

import pandas as pd
import numpy as np
import string

def solve(possibilities, constraints_sizes):
    solution = [None] * len(possibilities)

    def extend_solution(position):
        possible_subsets = [index for index, value in possibilities.iloc[position].iteritems() if value]
        for subset in possible_subsets:
            solution[position] = subset
            unique, counts = np.unique([a for a in solution if a is not None], return_counts=True)
            if all(length <= constraints_sizes[sub] for sub, length in zip(unique, counts)):
                if position >= len(possibilities)-1 or extend_solution(position+1):
                    return solution
        return None

    return extend_solution(0)


if __name__ == '__main__':

    constraints_sizes = [5, 5, 6]
    
    possibilities = pd.DataFrame([[False, True, False],
                                  [True, True, True],
                                  [True, True, True],
                                  [True, True, True],
                                  [True, False, False],
                                  [True, True, True],
                                  [True, True, True],
                                  [True, True, True],
                                  [True, False, False],
                                  [True, True, True],
                                  [True, True, True],
                                  [True, True, True],
                                  [False, True, True],
                                  [True, True, True],
                                  [True, True, True],
                                  [True, False, False]],
                                 index=list(string.ascii_lowercase[:16]))
    
    solution = solve(possibilities, constraints_sizes)

一种可能的预期解决方案[1, 0, 0, 1, 0, 1, 1, 1, 0, 2, 2, 2, 2, 2, 2, 0]

很遗憾,此代码无法找到解决方案(尽管它适用于前面的示例)。

我错过了什么?

非常感谢。

【问题讨论】:

    标签: python algorithm partitioning backtracking recursive-backtracking


    【解决方案1】:

    这个问题可以通过建立一个双向流网络来解决,其中一侧是项目,另一侧是子集,每个项目的盈余为 1,每个子集的赤字为(子集的大小),容量为 1 的弧从每个项目到它可以属于的每个子集。那么你需要这个网络上的最大流量; OR-Tools 可以做到这一点,但你有很多选择。

    【讨论】:

    • 感谢您的回复。您提到的工具和概念对我来说非常陌生。我希望找到一个可以用简单的 Python 标准库实现的简单算法。我也很想了解我的问题是否无法通过回溯解决,或者我的实施是否糟糕。再次感谢。
    • @Betcha 您可以在en.wikipedia.org/wiki/Edmonds%E2%80%93Karp_algorithm 找到可以实现的算法。从概念上讲,您正在做的是从随机将物品放入桶中开始,直到您卡住为止。然后,您开始进行广度优先搜索,寻找切换分配以获取更多项目的方法。很酷的是,您总是可以在多项式时间内取得成功。
    • @Betcha Backtracking 是一个类似的想法,但深度优先搜索而不是广度优先。问题是,如果您需要转换的决定是您的第一个任务,那么您必须在尝试之前探索整个搜索空间。在取得下一个进步之前,你会“陷入指数级兔子洞”。广度优先避免被卡住。
    【解决方案2】:

    @David Eisenstat 提到 OR-Tools 作为一个包来解决这类问题。

    多亏了他,我发现这个问题可以匹配他们的一个例子,Assignement with Task Sizes problem

    它比我从建议的“流网络”概念中理解的更符合我对问题的理解,但我很乐意讨论这个问题。

    这是我根据他们的示例实施的解决方案:

    from ortools.sat.python import cp_model
    
    
    def solve(possibilities, constraint_sizes):
        # Transform possibilities into costs (0 if possible, 1 otherwise)
        costs = [[int(not row[subset]) for row in possibilities] for subset in range(len(possibilities[0]))]
        
        num_subsets = len(costs)
        num_items = len(costs[0])
        
        model = cp_model.CpModel()
        
        # Variables
        x = {}
        for subset in range(num_subsets):
            for item in range(num_items):
                x[subset, item] = model.NewBoolVar(f'x[{subset},{item}]')
    
        # Constraints :
        # Each subset should should contain a given number of item
        for subset, size in zip(range(num_subsets), constraint_sizes):
            model.Add(sum(x[subset, item] for item in range(num_items)) <= size)
        
        # Each item is assigned to exactly one subset
        for item in range(num_items):
            model.Add(sum(x[subset, item] for subset in range(num_subsets)) == 1)
        
        # Objective
        objective_terms = []
        for subset in range(num_subsets):
            for item in range(num_items):
                objective_terms.append(costs[subset][item] * x[subset, item])
        model.Minimize(sum(objective_terms))
        
        # Solve
        solver = cp_model.CpSolver()
        status = solver.Solve(model)
        if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:
            solution = []
            for item in range(num_items):
                for subset in range(num_subsets):
                    if solver.BooleanValue(x[subset, item]):
                        solution.append(subset)
            return solution
        return None
    

    这里的诀窍是将可能性转化为成本(仅在可能的情况下为 0),并优化总成本。 一个可接受的解决方案的总成本应该为 0。

    它为上一个问题提供了正确的解决方案:

    possibilities = [[False, True, False],
                     [True, True, True],
                     [True, True, True],
                     [True, True, True],
                     [True, False, False],
                     [True, True, True],
                     [True, True, True],
                     [True, True, True],
                     [True, False, False],
                     [True, True, True],
                     True, True, True],
                     [True, True, True],
                     [False, True, True],
                     [True, True, True],
                     [True, True, True],
                     [True, False, False]]
    
    constraint_sizes = [5, 5, 6]
    
    solution = solver(possibilities, constraint_sizes)
    
    print(solution) # [1, 2, 1, 0, 0, 0, 2, 1, 0, 1, 2, 2, 2, 2, 1, 0]
    

    我现在还有两个问题:

    • 我们能否将优化目标(最小化成本)转化为硬约束(成本应该等于 0)?我想它可以减少计算时间。

    • 我怎样才能获得其他解决方案,而不仅仅是一个?

    我还在寻找没有任何库的纯 Python 解决方案...

    谢谢

    【讨论】:

    • 正如目前所写,您的答案尚不清楚。请edit 添加其他详细信息,以帮助其他人了解这如何解决所提出的问题。你可以找到更多关于如何写好答案的信息in the help center
    • 如果您有新问题,应该在新帖子中提出。 SO 不像讨论板那样工作