如何使用python在一个月内平均选择休假?

时间:2012-12-19 19:49:45

标签: python algorithm random

我将在一个日历月内随机为船员分配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实现这个的算法吗?

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

最好的问候

5 个答案:

答案 0 :(得分: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天。

答案 1 :(得分:3)

这是关键所在:

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

将问题改为每7天随机说2天(或根据需要将月份分成四段时间)。然后,您将获得均匀分布。 Martijn Pieters建议使用random.sample()

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

修改

正如tcaswell所观察到的,仍有一些情况下你最终连续十天值班。要解决这个问题,您可以每三天分配一天,创建十个列表,并从不影响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]

答案 2 :(得分: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]

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

答案 3 :(得分: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中所定义。

答案 4 :(得分: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个部分。