一个关于随机分组需求的Python方案与思考

需求描述:

这是一个实际生活中遇到的需求,一共有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

如果你有更好的方法,欢迎评论留言~

#End#

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值