我将在一个日历月内随机为船员分配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实现这个的算法吗?
并非所有日子都可以休息。
最好的问候
答案 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个部分。