需求描述:
这是一个实际生活中遇到的需求,一共有8个人需要在一周的5个工作日值班,每天都需要5个人完成值班任务,其中有一个人是个组长。要求组长在这5天里一共值班4次,其他人员均值班3次,每天的5个值班人员是8个人中的5人随机组合。
分析:
乍一看,好像有点无从下手,咱们细细分析就可以这样考虑,我们假设每个人都有分身术,组长一共有4个分身,其他人均有3个分身,这时正好25个人,组合后正好可以满足5天(25人)的值班要求。我们就可以对这25个假设对象进行随机分组,唯一的要求就是:同一身份的人不能分到一组中(即同一个人的分身不能分到一起)。分5组即可。
Python实现(Python3.7):
import random
# 构建分身数据源(25个分身对象)
def get_source_data():
data = []
for i in range(4):
data.append({"name":"组长", "role":i}) # role表示分身ID
for i in range(7):
for j in range(3):
data.append({"name": "组员" + str(i), "role": j})
return data
# 进行5次选择,选出5组
def group_by_random():
des_list = []
data_list = get_source_data()
for i in range(5): # 分5组
one_list = []
has_selected = {} # 标记某个人是否已经被添加进当前组中
while(len(one_list) < 5): # 直到每组中有5个不同的人(组中最多有同一个人的1个分身)
index = random.randint(0, len(data_list)-1)
person = data_list[index]
# 如果这个人(此处不区分“分身”,只区分“人”)没有被添加到当前组中,就添加该分身到组中,同时删除原数据列表中的这个分身对象
if not has_selected.__contains__(person["name"]):
one_list.append(person["name"])
data_list.pop(index)
has_selected[person["name"]] = True
des_list.append(one_list)
return des_list
if __name__ == '__main__':
for group in group_by_random():
print("分组:", group)
分组结果:
分组: ['组员1', '组长', '组员3', '组员2', '组员5']
分组: ['组员0', '组员4', '组长', '组员6', '组员1']
分组: ['组员5', '组员6', '组长', '组员3', '组员0']
分组: ['组员6', '组员2', '组员4', '组员1', '组员3']
分组: ['组员4', '组长', '组员0', '组员5', '组员2']
Process finished with exit code 0
问题发现与思考:
问题1、如果人数是成百上千甚至更多呢,数据集就会变得更大,总不能再去创建“分身”对象数据集吧!
问题2、对25个元素的数据集进行随机选取5个元素的时候,存在这样一种可能:前3次随机的结果都是同样的5个人, 这样在第4次随机的时候,导致数据集中的人数少于5个,虽然“分身”元素可能足够用。这种情况下,就会概率性地导致程序卡在group_by_random的while循环处,无法结束程序。问题严重啊~~~ 问题代码如下:
for i in range(5): # 分5组
one_list = []
has_selected = {} # 标记某个人是否已经被添加进当前组中
# 【注意】这里随机选取的时候,有可能出现连续多次选择到相同的5个人,当这个次数达到3次,就会出现意外情况。。。
while(len(one_list) < 5): # 直到每组中有5个不同的人(组中最多有同一个人的1个分身)
index = random.randint(0, len(data_list)-1)
person = data_list[index]
if not has_selected.__contains__(person["name"]):
one_list.append(person["name"])
data_list.pop(index)
has_selected[person["name"]] = True
des_list.append(one_list)
所以我们变换一种思路,“组长”比较特殊,因为他多一次呗被选中的机会,所以我们先把这次机会取出来,放在一边,不管他。这样所有的8个成员都是一样的情况了,每人可以被选择3次,一共24次。
1、搞一个列表存放这8人,并对列表内元素进行随机排序,
2、顺序的回归选取列表中的元素,选取5次,每次选连续的5个元素(例如:第一次选列表的前5个元素,第二次选取接下来的3个元素和列表头部的2个元素,以此类推......)。最后再把最后一组的最后一个数据删除(因为是多选的一个),添加上“组长”元素。这样就选择出了5组元素(在一个列表中,即列表中有5个列表,每个列表又包含5个元素)。
3、显然这是有规则的顺序选取,所以我们需要对这5组元素进行一个随机排序,以达到随机选取的效果。(这可能并不是严格意义上的机会均等随机,但足以满足需求了)
秘书,上代码(优化后):
import random
# 构建数据源
def get_source_data():
"""
获得一个数据列表,其内容为8个成员,其中一个为“组长”,其余为“组员”
:return:
"""
data = ["组员"+str(x) for x in range(7)]
data.append("组长")
return data
# 进行5次选择,选出5组
def group_by_order(data_list):
"""
对列表进行顺序读取,每5个放到一组中,当读取到列表的最后一个值,就跳转到列表的0索引处,继续读取。
最终返回一个包含5个列表的列表
:param data_list:
:return: 一个二维列表:5x5
"""
des_list = []
index = 0
for i in range(5):
group = []
for k in range(5):
group.append(data_list[index])
index += 1
if index > len(data_list)-1:
index = 0
if i is 4:# 对“组长”的情况特殊处理
group.pop(len(group)-1)
group.append("组长")
des_list.append(group)
return des_list
def set_random(data_list):
"""
对一个列表里的值得顺序随机打乱
:param data_list:
:return:
"""
return random.sample(data_list, len(data_list))
if __name__ == '__main__':
data = set_random(get_source_data())
groups = group_by_order(data)
groups = set_random(groups)
for group in groups:
print("分组:", group)
打印结果:
分组: ['组员3', '组员4', '组员6', '组员0', '组长']
分组: ['组员1', '组员5', '组员3', '组员4', '组员6']
分组: ['组员2', '组员1', '组员5', '组员3', '组员4']
分组: ['组员0', '组长', '组员2', '组员1', '组员5']
分组: ['组员6', '组员0', '组长', '组员2', '组长']
Process finished with exit code 0
如果你有更好的方法,欢迎评论留言~