3

我将在一个日历月内随机分配 8 天的假期给船员。

我想随机选择8天,休息天数应该尽量均匀。例如,我的意思是,不应在每月的前 8 天收集所有 8 天的假期。

例如:[1, 5, 8, 14, 18, 24, 27, 30] 是一个很好的分布。[1,2,3,4,26,27,28,29] 不是一个好的分布。

实际上,一个船员不能连续工作 7 天。每 7 天必须有 1 天的休息日。

所有的日子都一视同仁,即星期天本身不是休息日。船员也可以在周末工作。

我要一一选择休息日。不是8个人同时在一起的。

你能推荐一个使用python的算法来实现这个吗?

并非该月的所有日子都可以休息。

最好的祝福

4

5 回答 5

8

用于random.sample()从序列中获取随机集。列出可用的日期,然后将其传递给.sample()函数:

import sample
daysoff = [1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 15, 16, 17, 18, 19, 20]

picked = random.sample(daysoff, 8)

在上面的示例中,我使用了一个月中的某天,并且列表省略了某些日子(例如,星期日和该月的最后 10 天),然后我们从该人群中随机选择 8 天。

于 2012-12-19T19:53:35.157 回答
3

这是这里的关键:

Actually, a crew can't work 7 consecutive days. In every 7 days, there must be 1 day-off.

将问题改写为每 7 天随机选择 2 天(或酌情将一个月分为四个时间长度)。然后,您可以保证均匀分布。random.sample()按照 Martijn Pieters 的建议 使用。

您可以从第一周开始使用此技术生成两个值,然后如果您需要它们,请按顺序生成它们。

编辑:

正如 tcaswell 所观察到的,在某些情况下,您最终会连续值班十天。为了解决这个问题,您可以每三天分配一天休息,创建一个包含 10 天的列表,然后从不使 7 天连续标准无效的日期子集中随机删除两天。

或者,您可以继续使用原始算法生成列表,直到它符合标准,因为无论如何您很可能得到一个有效的解决方案。您必须编写某种验证函数,但这很容易做到,因为您只是在计算最长的连续天数。

代码:

第二个选项的实现。

import random
from itertools import chain
from itertools import count

def candidate(m):
    ''' Returns 2 days per week, in m days, where m is the length of the month. '''
    weeks = weeksmaker(m)
    return sorted(list(chain(*[random.sample(week, 2) for week in weeks])))

def weeksmaker(m):
    ''' Divides a month up into four weeks, randomly assigning extra days to weeks. '''
    weeks = [range(i, i+7) for i in xrange(1,29,7)]
    for i in range(m - 28):
        weeks[random.randint(1, len(weeks))-1].append(i)
    c = count(1)
    return [[c.next() for day in week] for week in weeks]

def valid(days, c):
    ''' Validity check. Cant work more than c consecutive days. '''
    for i in xrange(1, len(days)):
        if days[i] - days[i-1] > c:
            return False
    else:
        return True

def daysoff(m, n, c):
    ''' In month length m, need n days off, cant work more than c consecutive days. '''
    while True:
        days = candidate(n)
        if valid(days, c):
            return days

>>> for i in range(28, 32):
...     daysoff(i, 8, 7)
... 
[6, 7, 10, 14, 18, 20, 27, 28]
[4, 7, 10, 13, 19, 21, 23, 24]
[2, 4, 9, 13, 15, 20, 25, 27]
[1, 3, 9, 12, 18, 19, 24, 28]
于 2012-12-19T20:36:54.333 回答
1

您应该只拆分总天数。

无论需要多少天,无论总共有多少天,此代码都有效。

from random import randint
def foo(l, n):
    dist = round(len(l)/n)
    return [randint(l[i*dist], l[(i+1)*dist-1]) for i in range(n)]

In [1]: days = [i for i in range(1,31)]
In [2]: foo(days, 8)
Out[2]: [1, 4, 6, 9, 13, 16, 20, 27]

In [3]: mylist = [i for i in range(500)]
In [4]: foo(mylist, 5)
Out[4]: [80, 147, 250, 346, 448]

舍入会出现一些问题,列表索引可能会超出范围左右。

于 2012-12-19T20:42:20.207 回答
1

这(我认为)做了@Martijn 所做的事情,并且具有不包括连续天数的额外好处(例如,如果您不希望连续休息 8 天):

#Day selector

import random

Ndays = 8
daysoff = range(1,25)
concurrent_tol = 3

while True:
    cntr = 0
    sample = random.sample(daysoff, Ndays)
    sample.sort()
    for i in range(1,Ndays-1):
        if abs(sample[i]-sample[i-1]) == 1:
            cntr +=1
        if abs(sample[i]-sample[i+1]) == 1:
            cntr +=1

    if cntr<concurrent_tol:
        print "Found a good set of off-days :"
        print sample
        break
    else:
        print "Didn't find a good set, trying again"
        print sample

输出示例:

Didn't find a good set, trying again
[3, 4, 5, 6, 7, 8, 9, 11]
Didn't find a good set, trying again
[1, 5, 6, 7, 12, 14, 19, 20]
Didn't find a good set, trying again
[4, 5, 7, 9, 11, 15, 16, 20]
Didn't find a good set, trying again
[3, 4, 6, 7, 12, 13, 14, 23]
Didn't find a good set, trying again
[1, 7, 10, 12, 15, 16, 17, 22]
Didn't find a good set, trying again
[5, 7, 8, 11, 17, 18, 19, 23]
Didn't find a good set, trying again
[3, 8, 11, 12, 13, 15, 17, 21]
Didn't find a good set, trying again
[2, 5, 7, 8, 9, 12, 13, 21]
Found a good set of off-days :
[1, 2, 5, 12, 15, 17, 19, 20]

这还有一个额外的好处是看起来很丑。请注意,可能的天数为 1-24,如 daysoff 中所定义。

于 2012-12-19T22:08:26.333 回答
0

生成(并存储)所有有效工作计划的列表(通过蛮力......只有 30C8 种方法可以做到)。然后,您可以稍后从该列表中安全快速地进行选择。

import itertools
import numpy as np
good_lst = []
for days_off in itertools.combinations(range(30),8):
    if np.max(np.diff( (0,) + days_off + (30,))) < 7:
        good_lst.append(days_off)

(那里可能有一些错误的错误)

这在大约 5 分钟内在一台不错的机器上运行。您可能需要进行更多修剪,因为 (0, 1, 2, 3, 6, 12, 18, 24) 是有效的工作时间表,但涉及 6 个工作日的 4 个部分。

于 2012-12-19T21:05:46.643 回答