首页 > 解决方案 > 在不同的日子随机分配一组人时最好的方法是什么?

问题描述

所以,我有一个有趣的问题。但不确定这里是否是我必须发布的地方。

上下文

基本上我想建立不同的学习小组

但是,每个人在一周中都有一组可用的日子来参加一个学习课程。每个人只能参加一个学习小组

可用性示例

StudentA = [Tuesday, Wednesday]
StudentB = [Wednesday, Saturday]
StudentC = [Saturday]

TutorA= [Tuesday, Friday]
TutorB = [Tuesday]

我必须让学习小组


我的方法

我正在使用 python 3 我正在尝试生成所有可能的组合并使用可用人员列表过滤实际可能的情况:

  1. 我用代码命名每个学生可用性组合:

    • [星期二] => "p1"
    • [星期三] => "p2"
    • ...
    • [周二、周三、周四] => "p7"
  2. 和导师一样

    • [星期二] => "c1"
    • [星期三] => "c2"
    • ...
    • [周二、周三、周四] => "c7"
  3. 我使用 itertools.combinations_with_replacement 来制作所有

  1. 我将之前的两种组合组合成可能的研究组

  2. 我结合所有团队来生成场景

  3. 我使用限制过滤

    • 每个人被分配在一个研究组中
    • 每个人只参加一次
    • 学生比导师多

问题

正如人们所预料的那样,组合和排列正在产生大量的可能性。

我可以接受将天数限制为三天。

但是我仍然无法为我的普通计算机提供可接受的性能

有没有更好的方法我没有看到?我在想象一些线性代数是否可以将这个问题转化为一组方程,但不确定

Obs:学生和导师的人数要多得多(每个人在 20 到 50 人之间)

标签: pythoncombinationspermutation

解决方案


上下文

按每周 7 天的可用性指定学生

例子:

 [
    [1, 1, 0, 0, 0, 0, 0],
    [1, 0, 1, 0, 0, 0, 1],
    [1, 1, 0, 1, 0, 1, 1],
    [0, 1, 0, 1, 0, 1, 0]
  ]

每行代表一个学生(本例中为 4 个学生) 每列代表一周中的某一天(即 7 个​​),分别为可用或不可用的 1 或 0。

所以[1, 1, 0, 0, 0, 0, 0]意味着学生 0 在一周的前两天(即周一和周二)有空。

TA 可用性以类似方式指定

例子:

[
    [1, 1, 1, 1, 1, 1, 1],
    [1, 0, 0, 0, 0, 0, 0],
    [1, 0, 0, 0, 0, 0, 1]
]

显示有三个助教,每天都有第一个助教。

会议安排如下:

               7 Days with N sessions per day numbered 0 to N-1

Session    Monday   Tuesday  Wednesday  Thursday  Friday   Saturday   Sunday
0          session  session  session   session   session   session    session
1          session  session  session   session   session   session    session
2          session  session  session   session   session   session    session
3          session  session  session   session   session   session    session
            ...
N-1        session  session  session   session   session   session    session

N 是未知的,但我们可以选择一个适当大的值(即,如果 N 得到只是导致更多的会话没有分配给学生或助教)。

约束是:

  • 每节 2 至 4 名学生
  • 学生每周只有一堂课
  • 学生只在他们可用的日子被分配一个会话
  • 每个会话 2 到 3 个助教
  • 助教仅在其可用的日期分配会话

目标是找到满足约束的学生和助教的作业。

方法

使用 Google ortools 约束求解器

ortools 允许将此组合问题描述为搜索问题

参考:ortools youtube Tutorail

代码

from ortools.sat.python import cp_model    # Google Constraint Modeling Tool (https://developers.google.com/optimization/cp/cp_solver)
from tabulate import tabulate              # For displaying results in table form (https://pypi.org/project/tabulate/)

week_days = ("Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday")
                                      
def main(student_availability, ta_availability):
    ########################################################
    # Information Setup
    ########################################################
    num_days = len(week_days)     # days in a week we are using
                                    
    num_students = len(student_availability)
    num_days = len(student_availability[0])
    all_students = range(num_students)
    all_days = range(num_days)
    
    # Show student availability
    special = u'\u2713'
    tmp_table = [[special if c else 'x' for c in row] for row in student_availability]
    display_table(tmp_table, "Students", "Students Availability")
 
 
    num_tas = len(ta_availability)
    all_tas = range(num_tas)
    
    # Show TA availability
    tmp_table = [[special if c else 'x' for c in row] for row in ta_availability]
    display_table(tmp_table, "     TAs", "\nTA Availability")
    
    # Learning sessions 
    #  Model as max sessions per day, 7 days a weeks
    #  The solver will decide which sessions are assigned students and TAs
    max_sessions_per_day = num_students       # Would actually use a lot fewer sessions per day
    all_sessions = range(max_sessions_per_day)
 
    ########################################################
    # Creates the model
    ########################################################
    model = cp_model.CpModel()
    
    ########################################################
    # Specify Constraints
    ########################################################
    
    session_zero = {}             # sessions with zero students and TAs (i.e. unused)
    session_bound = {}            # sessions which satisfy the student and TA occupancy bounds
    for d in all_days:
        for session in all_sessions:
            session_zero[(d, session)] = model.NewBoolVar('zero%id%ig' % (d, session))
            session_bound[(d, session)] = model.NewBoolVar('bound%id%ig' % (d, session))
            model.Add(session_zero[(d, session)] + session_bound[(d, session)]==1)  # Sessions are used or unused

    # Student Constraints
    student_group = {}
    for s in all_students:
        for d in all_days:
            for session in all_sessions:
                student_group[(s, d, session)] = model.NewBoolVar('student_assigned%is%id%ig' % (s, d, session))
                
    # Each used session has between 2 & 4 students
    for d in all_days:
        for session in all_sessions:
            # Meeting define by (d, session)
            students_assigned = sum(student_group[(s, d, session)] for s in all_students)
            model.Add(students_assigned==0).OnlyEnforceIf(session_zero[(d, session)])
            model.Add(students_assigned >= 2).OnlyEnforceIf(session_bound[(d, session)])
            model.Add(students_assigned <= 4).OnlyEnforceIf(session_bound[(d, session)])
                
    # Every student is assigned one session in the week on their available days
    for s, row in enumerate(student_availability):
        available_days = [d for d, v in enumerate(row) if v > 0]
        model.Add(sum(student_group[(s, d, session)] for d in available_days for session in all_sessions) == 1)
        
    # No Studentss are assigned sessions on their unavailable days
    for t, row in enumerate(student_availability):
        unavailable_days = [d for d, v in enumerate(row) if v == 0]
        model.Add(sum(student_group[(t, d, session)] for d in unavailable_days for session in all_sessions) == 0)
                
        
    # TA Constraint
    ta_group = {}
    for t in all_tas:
        for d in all_days:
            for session in all_sessions:
                ta_group[(t, d, session)] = model.NewBoolVar('ta_assigned%is%id%ig' % (t, d, session))

            
    # Each session has between 2 & 3 TAs
    for d in all_days:
        for session in all_sessions:
            ta_assigned = sum(ta_group[(t, d, session)] for t in all_tas)
            model.Add(ta_assigned==0).OnlyEnforceIf(session_zero[(d, session)])
            model.Add(ta_assigned >= 2).OnlyEnforceIf(session_bound[(d, session)])
            model.Add(ta_assigned <= 3).OnlyEnforceIf(session_bound[(d, session)])

    # TAs are assigned on available days
    for t, row in enumerate(ta_availability):
        available_days = [d for d, v in enumerate(row) if v > 0]
        model.Add(sum(ta_group[(t, d, session)] for d in available_days for session in all_sessions) >= 0)
        
    # No TAs are assigned on their unavailable days
    for t, row in enumerate(ta_availability):
        unavailable_days = [d for d, v in enumerate(row) if v == 0]
        model.Add(sum(ta_group[(t, d, session)] for d in unavailable_days for session in all_sessions) == 0)
                
    # Creates the solver and solve.
    solver = cp_model.CpSolver()
    
    # Sets a time limit of 10 seconds.
    solver.parameters.max_time_in_seconds = 10.0
 
    # Display the first five solutions.
    a_few_solutions = range(2)
    solution_printer = StudentsPartialSolutionPrinter(student_group,
                                                      num_students, 
                                                      ta_group, 
                                                      num_tas,
                                                      num_days, 
                                                      max_sessions_per_day,
                                                      student_availability,
                                                      ta_availability,
                                                      a_few_solutions)
    
    status = solver.Solve(model, solution_printer)
    print()
    print('Status = %s' % solver.StatusName(status))
    print('Number of solutions found: %i' % solution_printer.solution_count())


    # Statistics.
    print()
    print('Statistics')
    print('  - conflicts       : %i' % solver.NumConflicts())
    print('  - branches        : %i' % solver.NumBranches())
    print('  - wall time       : %f s' % solver.WallTime())
    print('  - solutions found : %i' % solution_printer.solution_count())

class StudentsPartialSolutionPrinter(cp_model.CpSolverSolutionCallback):
    """Print intermediate solutions."""

    def __init__(self, 
                 student_group,
                 num_students, 
                 ta_group, 
                 num_tas,
                 num_days, 
                 max_sessions_per_day, 
                 student_availability,
                 ta_availability,
                 sols):
        cp_model.CpSolverSolutionCallback.__init__(self)
        self._student_group = student_group
        self._num_students = num_students
        self._ta_group = ta_group
        self._num_tas = num_tas
        self._num_days = num_days
        self._max_sessions_per_day = max_sessions_per_day
        self._student_availability = student_availability
        self._ta_availability = ta_availability
        self._solutions = set(sols)
        self._solution_count = 0

    def on_solution_callback(self):
        if self._solution_count in self._solutions:
            print('\nSolution %i' % self._solution_count)
            # Student Scheduling
            table = [[u'\u2713' if col else 'x' for col in row] for row in self._student_availability]
            
            for session in range(self._max_sessions_per_day):
                    for d in range(self._num_days):
                        for s in range(self._num_students):
                            if self.Value(self._student_group[(s, d, session)]):
                                table[s][d] = table[s][d] + f" {week_days[d][:3]}-{session}"
            print()    
            display_table(table, "Students", "Students Scheduling")
                      
   
            # TA scheduling
            table = [[u'\u2713' if col else 'x' for col in row] for row in self._ta_availability]
            for session in range(self._max_sessions_per_day):
                    for d in range(self._num_days):
                        for s in range(self._num_tas):
                            if self.Value(self._ta_group[(s, d, session)]):
                                table[s][d] = table[s][d] + f" {week_days[d][:3]}-{session}"
                
            
            print()
            display_table(table, "     TAs", "TA Scheduling")

        self._solution_count += 1
 
    def solution_count(self):
        return self._solution_count

def display_table(data, data_category, message):
    ''' Display data in tabular form'''
    table = [[f'{idx:>8}'] + row for idx, row in enumerate(data)]
    print(message)
    print(tabulate(table, headers=(data_category,) + week_days, tablefmt="rst"))
    
if __name__ == '__main__':
    # Student Information
    student_availability = [
        [1, 1, 0, 0, 0, 0, 0],
        [1, 0, 1, 0, 0, 0, 1],
        [1, 1, 0, 1, 0, 1, 1],
        [0, 1, 0, 1, 0, 1, 0]
    ]
    
    # TA Information
    ta_availability = [
        [1, 1, 1, 1, 1, 1, 1],
        [1, 0, 0, 0, 0, 0, 0],
        [1, 0, 0, 1, 0, 0, 1]
    ]
    
    # Find solutions
    main(student_availability, ta_availability)

输出

指定的问题需要三个会话——星期一 (Mon-3) 和星期四 (Thu-0)。

Students Availability
==========  ========  =========  ===========  ==========  ========  ==========  ========
  Students  Monday    Tuesday    Wednesday    Thursday    Friday    Saturday    Sunday
==========  ========  =========  ===========  ==========  ========  ==========  ========
         0  ✓         ✓          x            x           x         x           x
         1  ✓         x          ✓            x           x         x           ✓
         2  ✓         ✓          x            ✓           x         ✓           ✓
         3  x         ✓          x            ✓           x         ✓           x
==========  ========  =========  ===========  ==========  ========  ==========  ========

TA Availability
==========  ========  =========  ===========  ==========  ========  ==========  ========
       TAs  Monday    Tuesday    Wednesday    Thursday    Friday    Saturday    Sunday
==========  ========  =========  ===========  ==========  ========  ==========  ========
         0  ✓         ✓          ✓            ✓           ✓         ✓           ✓
         1  ✓         x          x            x           x         x           x
         2  ✓         x          x            ✓           x         x           ✓
==========  ========  =========  ===========  ==========  ========  ==========  ========

Solution 0

Students Scheduling
==========  ========  =========  ===========  ==========  ========  ==========  ========
  Students  Monday    Tuesday    Wednesday    Thursday    Friday    Saturday    Sunday
==========  ========  =========  ===========  ==========  ========  ==========  ========
         0  ✓ Mon-3   ✓          x            x           x         x           x
         1  ✓ Mon-3   x          ✓            x           x         x           ✓
         2  ✓         ✓          x            ✓ Thu-0     x         ✓           ✓
         3  x         ✓          x            ✓ Thu-0     x         ✓           x
==========  ========  =========  ===========  ==========  ========  ==========  ========

TA Scheduling
==========  ========  =========  ===========  ==========  ========  ==========  ========
       TAs  Monday    Tuesday    Wednesday    Thursday    Friday    Saturday    Sunday
==========  ========  =========  ===========  ==========  ========  ==========  ========
         0  ✓ Mon-3   ✓          ✓            ✓ Thu-0     ✓         ✓           ✓
         1  ✓ Mon-3   x          x            x           x         x           x
         2  ✓         x          x            ✓ Thu-0     x         x           ✓
==========  ========  =========  ===========  ==========  ========  ==========  ========

Status = OPTIMAL
Number of solutions found: 1

Statistics
  - conflicts       : 0
  - branches        : 0
  - wall time       : 0.014649 s
  - solutions found : 1

推荐阅读