【问题标题】:How to solve this employer schedule problem, functionally?如何在功能上解决这个雇主日程安排问题?
【发布时间】:2021-07-03 20:17:17
【问题描述】:

我提出这个问题的目的是获得一个工作起点并解决这个问题。我不打算有一个完整的答案来神奇地解决我的问题。

上下文

一家公司有 66 个雇主。然而,由于 COVID 大流行,他们每天只有 41 张办公椅(又名位置)。我所说的天数是指一周(周一、周二、周三、周四、周五)

为了解决这个问题,他们创建了三类工人:

  1. 有固定地点,没有家庭办公室的人 (10)
  2. 只有 1 天在家办公的人 (7)
  3. 将有 2 天在家办公的人 (49)

我需要将所有工人与所有椅子结合起来,这样所有 41 把椅子都会被填满。

有一些限制(还有更多,但为了简单起见,我只说 2 个):

  1. 员工不能连续几天在家办公
  2. 工人不能选择在周五和周一在家办公。 (例如:工人 1 有 2 天在家办公,然后他选择星期五和星期一,因此他将在家办公 4 天)

我做了什么

好的,所以我开始考虑如何解决这个问题,但是我被卡住了......

我现在做的是数据模型和一些辅助函数,但是我很难想出一个真正的解决方案。我该如何开始?不是简单的组合题,是我的第一个这种类型。

这是我的数据模型:

data Day = Mon | Tue | Wed | Thu | Fri deriving (Show)

data Category = Fixed | OneDay | TwoDays deriving (Show)

data Function = Director | Manager | Common deriving (Show)

data Worker = W { name       :: String
                , occupation :: String
                , category   :: Category
                , group      :: String
                , squad      :: String
                , function   :: Function
                } deriving (Show)

data Schedule = S [(Worker, [Day])] deriving (Show)

type Workers = [Worker]

-- | Helpers

numWorkers :: Int
numWorkers = 66

numChairs :: Int
numChairs = 41

days :: [Int]
days = [1..5]

haveNoDays :: Worker -> Bool
haveNoDays W{category=Fixed} = True
haveNoDays _                 = False

haveOneDay :: Worker -> Bool
haveOneDay W{category=OneDay} = True
haveOneDay _                  = False

haveTwoDays :: Worker -> Bool
haveTwoDays W{category=TwoDays} = True
haveTwoDays _                   = False

decreaseCategory :: Worker -> Worker
decreaseCategory w@W{category=c} =
  case c of
    Fixed   -> w
    OneDay  -> w {category = Fixed}
    TwoDays -> w {category = OneDay}

-- | Main functions

schedule :: Workers -> Schedule
schedule _ = S []

【问题讨论】:

  • 对于暴力破解,您需要一个函数schedules :: Int -> Workers -> [Schedule] 来生成所有可以想象的 N 周时间表,从每天都没有人在办公室的那些开始到每天每个人都来的那些.然后schedule = head . filter isValidSchedule . schedule 2
  • 强烈建议查看像 IBM CPLEX 这样的 CP 求解器(约束编程)。它是为这类事情而构建的。
  • Google OR 工具也是一个不错的选择。
  • 我和@MLavrentyev 在一起。不要打扰 Haskell。直接去SAT求解器或类似的; z3 或 yices 都会立即解决这个问题。如果你想要一种类似 Haskell 的语言来编写 SAT 查询,请查看cryptol——可行的方法是编写一个函数式程序,当分配人员到椅子上时,检查该分配是否有效(满足您列出的所有约束)。然后,cryptol repl 中的:sat 将向求解器请求满足约束的分配。
  • 我部分同意@DanielWagner,但只是部分同意。是的,我也会使用 SMT 求解器,但通过出色的 sbv Haskell 库。我真的很喜欢用它来解决这样的问题。

标签: haskell functional-programming linear-programming


【解决方案1】:

由于您正在寻找起点而不是解决方案,我将介绍一种蛮力方法,您可以使用它来考虑从功能上解决此问题,但在计算上 实用.

再次强调:这不是一个实用的解决方案,即使采用蛮力解决方案,您也可能做得更好

我还将尝试解决 1 周版本的问题,假设 1 周的时间表将无限期重复。请注意,此假设不一定是正确的(可能存在需要 2 周才能分配的解决方案)。如果我们接受这个假设,您的一个限制条件就是有 2 天假期的人不能在周一和周五都休假。

我将使用您的数据模型的真正简化版本。

data Location = Home | Office deriving (Eq, Show)

让我们忽略总是在办公室的工人。他们每天总是消耗相同数量的椅子。实际上,如果您有 41 把椅子,每天有 10 名员工在场,那么您就有 31 把椅子留给有灵活日程安排的员工。

对于每周在家一次的员工,他们的每周日程安排将是以下排列之一

[Home, Office, Office, Office, Office]

而您每周在家两次的员工将是

的排列之一
[Home, Home, Office, Office, Office]

但您将排除 [Home,Office,Office,Office,Home],因为这会违反您的约束。

让我们命名一些类型并严重滥用列表以使事情变得简单:

type WorkerSchedule = [Location]   -- a schedule for an individual worker
type PossibleSchedule = [WorkerSchedule]  -- a list of possible schedules for a worker
type FullSchedule = [WorkerSchedule] -- represents a list of workers with the schedule that they will work
type WorkerSchedules = [PossibleSchedule] -- represents the brute force pool of schedules
type ChairCount = Int

这是一个愚蠢的蛮力算法:

  1. 为您的每个工作人员创建一个PossibleSchedule,它表示可以在给定周内安排工作人员的所有可能方式。
  2. 创建工作人员可能的日程安排列表(一种类型的 7 个,另一种类型的 49 个)
  3. 创建您每天可用的椅子列表 [31,31,31,31,31]
  4. 从工作人员列表的开头开始,为第一个工作人员提供第一个可能的时间表,并且所有椅子都可用。
  5. 为其他员工找到解决问题的方法,但在员工在办公室的每一天都减少一把椅子。 一种。如果有解决方案,那么我们只需按照我们为其他工人找到的解决方案制定时间表即可。 湾。如果没有解决方案,请使用下一个可能的时间表为第一个工作人员重试。如果没有更多可能的时间表,那么就没有解决方案。

您最终可能会得到如下所示的内容:

findSchedule :: [ChairCount] -> WorkerSchedules -> Maybe Fullschedule
findSchedule chairs [] = Just []        --Nobody left to schedule!
findSchedule chairs ([]:xss) = Nothing  --No other schedules I can try for this worker
findSchedule chairs ((x:xs):xss) =
  case buildChairCountAfterWorker chairs x of
    Nothing -> findSchedule chairs (xs:xss) --Try next schedule
    Just newChairs -> case findSchedule newChairs xss of
                        Nothing -> findSchedule chairs (xs:xss) --Try next schedule 
                        Just sched -> Just (x:sched) --Return solution

在这种情况下,buildChairCountAfterWorker 是一个辅助函数,它获取椅子列表、工人的建议时间表,并返回 Maybe [ChairList],如果某一天没有足够的椅子,这将一无所有,或以其他方式提供其他人剩余的椅子数量。

所以....这是功能性和“正确”的,但不实用。假设您的员工是固定的(您有问题中描述的 56 名员工)。

*Constraints> findSchedule [5,5,5,5,5] allEmployees
Nothing
(0.07 secs, 43,830,416 bytes)

好的 - 这很合理。我们显然不能让所有这些人都坐在 5 把椅子上。

*Constraints> findSchedule [51,51,51,51,51] allEmployees
Just <schedule removed for clarity>
(0.13 secs, 35,608,720 bytes)

太棒了。当我们有很大的空间来寻找解决方案时,我们不必探索解决方案的空间很远。

*Constraints> findSchedule [6,6,6,6,6] allEmployees 
Nothing
(0.49 secs, 373,728,960 bytes)
*Constraints> findSchedule [7,7,7,7,7] allEmployees 
Nothing
(13.56 secs, 9,336,407,864 bytes)
*Constraints> findSchedule [8,8,8,8,8] allEmployees 
Nothing
(328.77 secs, 250,233,745,768 bytes)

呃 - 这看起来不太好。如果我们将自己限制在 8 把椅子上,仍然需要 5 分钟才能深入到解决方案空间以确认我们没有解决方案。

从顶部接近更受约束的解决方案也好不到哪里去:

*Constraints> findSchedule [47,47,47,47,47] allEmployees
(149.57 secs, 112,385,139,968 bytes)

您几乎可以肯定比该算法做得更好,但要点可能是您应该使用原始帖子中 cmets 中讨论的专用求解器。愚蠢的蛮力总是会遇到一个看起来很像 9**(员工数量)的指数问题。

【讨论】:

    猜你喜欢
    • 2020-07-21
    • 2020-11-19
    • 2022-11-10
    • 2019-09-10
    • 2020-06-29
    • 2017-05-07
    • 2021-10-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多