python生成随机姓名、工号、电话,自动排班
我们直接修改下代码,## 定义一个类"""其实只需要工号、姓名即可,但是为了看起来很真,强行加了手机号随机生成一个姓名、手机号定义为静态方法,内部调用"""self.length = length # 需要的人数self.path = path # 输出路径"""生成一个姓名"""fake = Faker("zh_CN") # 生成一个随机姓名"""生成一个手机号""""""生成指定长度姓名"""
突然看到一个问题,自动排班,想了一下,这主要是个逻辑问题,自行手动实现:
基于以下假设:
假设1.工厂,机器24小时不停转,人工作12小时换班,人不可连续上两个班;
一般一次排一周的表,排一个月感觉不太实际,Mon_1即表示周一白班,_2即周一夜班;
假设2.比如有10台机器,至少需要20个人,一般要多一点余量,我们这里以23人计;(后续思考,Every dog has its day ,其实一周最多应该六天,要么周日统一休息,稍微改下代码即可,如果抽空休息的话,得多加点判断,这里就不搞了)
假设3.本意是避免一个人周一上了夜班,以后一直上夜班,实际上在两班倒的情况下,10台机器23个人,只多3个,并不能很好地解决这个问题,最关键的是增加人手,代码实现上,优先排本周累计上班次数少的;
包含以下内容:
(1)实现伪造工号、姓名
(2)实现排班
(3)测试代码
(4)不分享完全实用代码,分享过程与思考
一、生成随机不重复姓名
import pandas as pd
from faker import Faker
import time,datetime
import random
from random import sample
import numpy as np
from collections import Counter
pd.set_option('display.max_columns', 20)
Faker可以帮助我们迅速生成,不过数量稍多,可能有重复的,加手机号是为了看起来更加真实而已,并不用手机号。
# 生成一个随机姓名
fake = Faker("zh_CN")
print('随便生成一个姓名')
print(fake.name())
# 生成一个随机手机号
print('随便生成一个随机手机号')
print(fake.phone_number())
随便生成一个姓名 范龙 随便生成一个随机手机号 13603006196
# 生成多个随机姓名
number = [i for i in range(1,301)]
names =[fake.name() for i in range(1,301)]
temp = dict(zip(number,names))
# print(dict(zip(number,names)))
# 检查名字是否有重复
check_dict={} # 临时字典
for key,value in temp.items():
if value not in check_dict:
check_dict[value]=1
else:
check_dict[value]+=1
for a,b in check_dict.items():
if b >1:
print('重复的名字有---------')
print(a,b)
重复的名字有--------- 王静 2 重复的名字有--------- 吴建军 2 重复的名字有--------- 李雷 2 重复的名字有--------- 李利 2 重复的名字有--------- 李鑫 2 重复的名字有--------- 王桂英 2
果然人一多,就有重复的,现实中,人名重复很常见,实际上排班应该用工号,工号肯定是唯一的,但这里我们演示都用姓名,因为一堆工号会让看官看的懵逼;
1.1正确姿势
应该加点判断进去:
def make_a_name():
"""生成一个姓名"""
fake = Faker("zh_CN") # 生成一个随机姓名
return fake.name()
def make_a_phone():
"""生成一个手机号"""
fake = Faker("zh_CN")
return fake.phone_number()
def make_namelist(length=200):
"""生成指定个数姓名"""
name_list=[]
while len(name_list) < length: # 需要的人数
if make_a_name() not in name_list: # 去重
name_list.append(make_a_name())
return name_list
def make_phonelist(length=200):
"""生成指定个数的手机号"""
phone_list=[]
while len(phone_list) < length: # 需要的人数
if make_a_phone() not in phone_list: # 去重
phone_list.append(make_a_phone())
return phone_list
def make_id(length=200):
"""生成指定个数的不重复员工号"""
id_list = []
while len(id_list)<length:
one_id = random.randint(100,1000) # 从100到1000随机生成一个
if one_id not in id_list:
id_list.append(one_id)
return id_list
def combine_them():
"""合并数据并写入到excel中"""
ii = make_id(length=200)
nn = make_namelist(length=200)
pp = make_phonelist(length=200)
df = pd.DataFrame(data={'工号':ii,'姓名':nn,'手机号':pp})
# print(df)
df.to_excel('d:/fake_names.xlsx',sheet_name='Shit1',index=False)
1.2定义一个类:
我们直接修改下代码,
## 定义一个类
class MakeFakes():
"""
其实只需要工号、姓名即可,但是为了看起来很真,
强行加了手机号
随机生成一个姓名、手机号定义为静态方法,内部调用
"""
def __init__(self,length ,path):
self.length = length # 需要的人数
self.path = path # 输出路径
@staticmethod
def make_a_name():
"""生成一个姓名"""
fake = Faker("zh_CN") # 生成一个随机姓名
return fake.name()
@staticmethod
def make_a_phone():
"""生成一个手机号"""
fake = Faker("zh_CN")
return fake.phone_number()
def make_namelist(self):
"""生成指定长度姓名"""
name_list=[]
while len(name_list) < self.length: # 需要的人数
if MakeFakes.make_a_name() not in name_list: # 去重
name_list.append(MakeFakes.make_a_name())
return name_list
def make_phonelist(self):
"""生成指定长度手机号"""
phone_list=[]
while len(phone_list) < self.length: # 需要的人数
if MakeFakes.make_a_phone() not in phone_list: # 去重
phone_list.append(MakeFakes.make_a_phone())
return phone_list
def make_id(self):
"""生成不重复员工号"""
id_list = []
while len(id_list)<self.length:
one_id = random.randint(100,1000) # 从100到1000随机生成一个
if one_id not in id_list:
id_list.append(one_id)
return id_list
def combine_them(self):
"""合并数据并写入到excel中"""
ii = self.make_id()
nn = self.make_namelist()
pp = self.make_phonelist()
df = pd.DataFrame(data={'工号':ii,'姓名':nn,'手机号':pp})
df.to_excel(self.path,sheet_name='Shit1',index=False)
return df
这生成随机姓名,还是要稍微运行一下,并不是瞬间就行;
使用一下,我帮各位看了,没有有重复的;
ceshi = MakeFakes(230,'d:/names_230.xlsx')
ceshi.combine_them()
二、排班
先搞简单点,比如10台机器,23个人,多的3个是留着应对突发状况的;
ceshi2 = MakeFakes(23,'d:/names_23.xlsx')
ceshi2.combine_them()
后续想了一下,10*2*7=140,140/6=23.3,实际上应该要24个,这样才能保证每个人最多上六天班,不过这里只是演示;
我们在D盘生成了一个23人的先看看效果,230人的,用于验证;
2.1逻辑演示
# 以姓名演示
data = pd.read_excel('d:/names_230.xlsx')
id_list=data['姓名']
# print(id_list)
# 100台机器
machines = list(range(1,101))
cols = ['Mon_1','Mon_2','Tue_1','Tue_2','Wed_1','Wed_2','Thu_1','Thu_2','Fri_1','Fri_2','Sat_1','Sat_2','Sun_1','Sun_2']
work_df = pd.DataFrame(index=list(range(1,101)),columns=cols)
这里展示下逻辑:
# 可以上班的名单,必须转先为列表
available_names = id_list.values.tolist()
# 第一天白,随便抽100个人,变量无贬义
dogs = sample(available_names,100)
work_df.loc[:,'Mon_1'] = dogs
# 第一天夜班,当天上白班的不能再上了
for value in dogs:
available_names.remove(value)
dogs =sample(available_names,100)
work_df.loc[:,'Mon_2'] = dogs
# 第二天白,重置可上班的
# 要去除上一轮夜班
available_names = id_list.values.tolist()
for value in dogs:
available_names.remove(value)
dogs =sample(available_names,100)
work_df.loc[:,'Tue_1'] = dogs
可以看到,第一天白班抽100个人,第一夜,需先重置可上班人员名单,再删除刚上完班的,循环下去(该方法有漏洞,往下看,本文主打一个草履虫可懂)
2.2初次尝试
# 可以看到,后面的基本是重复上一轮的
# 写出方法
def renew(dog,available):
for value in dog:
available.remove(value)
return available
def make_work_schedule(machines,excel_path):
cols = ['Mon_1','Mon_2','Tue_1','Tue_2','Wed_1','Wed_2','Thu_1','Thu_2','Fri_1','Fri_2','Sat_1','Sat_2','Sun_1','Sun_2']
work_df = pd.DataFrame(index=machines,columns=cols)
# 获取工号
data = pd.read_excel(excel_path)
id_list=data['姓名']
# 所有工号
available_names = id_list.values.tolist()
for c in cols:
if c !='Mon_1':
available_names = id_list.values.tolist()
# 更新下次可上班的
available_names = renew(dogs,available_names)
# 第一天白
dogs = sample(available_names,10)
work_df.loc[:,c] = dogs
return work_df
此处得思考了,会不会分配不均;比如计件的工厂,上班少了工资会低,固定工资的工厂,上班少的高兴,被安排上七天班的,肯定要很不公平,那我们来看看:
# 查看情况
def get_work_times(df):
temp_list =[]
name_list,time_list=[],[]
"好像只能行、列循环判断,用pandas的负面影响"
for col in range(0,df.shape[1]):
for row in range(0,df.shape[0]):
value = df.iloc[row,col]
if not pd.isna(value):
temp_list.append(value)
cc = Counter(temp_list)
for k,v in cc.items():
name_list.append(k)
time_list.append(v)
return name_list,time_list
a,b = get_work_times(schedule1)
dict(zip(a,b))
可以看到,有的只有4天,有的是7天,所以有问题,要改一下:
2.3 改进
自己看代码,注释写了很多:
def renew(dog,available):
"""该方法不变"""
for value in dog:
available.remove(value)
return available
def make_work_schedule2(machines,excel_path):
cols = ['Mon_1','Mon_2','Tue_1','Tue_2','Wed_1','Wed_2','Thu_1','Thu_2','Fri_1','Fri_2','Sat_1','Sat_2','Sun_1','Sun_2']
work_df = pd.DataFrame(index=machines,columns=cols)
# 获取工号
data = pd.read_excel(excel_path)
id_list=data['姓名']
# 所有工号
available_names = id_list.values.tolist() # 初始化,用于第一次判定
iter_count = 0 # 记录执行了多少次,后面要用
n = 0 # 记录多少个被抽到较少的,提前安排上班
for c in cols:
iter_count+=1
if iter_count>1: # 第二轮循环开始要先重置该list,不然减没了
all_names = id_list.values.tolist() # 所有员工名单
available_names = renew(dogs,all_names) # 返回去除刚上完班的
if iter_count==3:
"""
在第三轮,就把每抽到的立马先安排上班
指定第三轮,是怕出现极特殊情况,很多轮就是抽不中他
eager_names 即优先安排上班的员工
"""
names,times = get_work_times(work_df)
# 差集,挑出两轮没被抽到的
eager_names = list(set(all_names)-set(names))
n = len(eager_names)
if iter_count>3:
"""
从第四轮开始,时刻注意将被抽到较少的提前排班,
经过三次排班,所有人至少被抽到一次
"""
names,times = get_work_times(work_df)
# 找到被抽到次数最少的,可能会>10
less_names = [names for names, x in zip(names, times) if x == min(times)]
# 求交集,刚下班的不可能让其上班
eager_names = list(set(less_names)&(set(available_names)))
n = len(eager_names)
if 10>n>0:
# 先安排上班少的
dogs = sample(eager_names,n)
# 再安排可上班的,挑满10个
temp = sample(list(set(available_names)-(set(dogs))),10-n)
dogs.extend(temp)
work_df.loc[:,c] = dogs
if n>=10:
"会出现n>10的情况,那就挑10个咯"
dogs = sample(eager_names,10)
work_df.loc[:,c] = dogs
if n ==0:
# 基本是第一、二次才会执行
dogs = sample(available_names,10)
work_df.loc[:,c] = dogs
return work_df
直接测试:
# 生成数据
machines = list(range(1,11))
excel_path='d:/names_23.xlsx'
schedule2 = make_work_schedule2(machines,excel_path)
schedule2
# 查看是否不均
aa,bb = get_work_times(schedule2)
dict(zip(aa,bb))
循环100次,每次1个周的班,即排了100周的班,看不均程度:
min(result_dict.values()),max(result_dict.values())
# (603, 616)
import matplotlib.pyplot as plt
import seaborn as sns
sns.barplot(x=list(result_dict.keys()),y=list(result_dict.values()))
plt.xticks(range(0,len(result_dict)),labels=result_dict.keys(),color='red',rotation=90)
次数多了,还是会有区别,因为我们本意是只排一周的班,不过100周起码是两年多的时间,差10来次,其实可以接受;
不过此处有个问题,并未考虑 下一周的班和上一周班的关系,比如我上一周白班上得多,下一周是不是夜班要多一点?
2.4封装
class Gaoshiqing():
def __init__(self,human_numbers,machine_numbers,judge_col,excel_path):
self.human_numbers = human_numbers # 需要的人数
self.machine_numbers = machine_numbers # 机器数
self.excel_path = excel_path # 文件路径
self.judge_col = judge_col # 用工号还是姓名判定
def get_work_times(self,df):
# df = pd.read_excel(self.excel_path)
temp_list =[]
name_list,time_list=[],[]
for col in range(0,df.shape[1]):
for row in range(0,df.shape[0]):
value = df.iloc[row,col]
if not pd.isna(value):
temp_list.append(value)
cc = Counter(temp_list)
for k,v in cc.items():
name_list.append(k)
time_list.append(v)
return name_list,time_list
@staticmethod
def renew(dog,available):
for value in dog:
available.remove(value)
return available
def make_work_schedule(self):
machines = range(1,self.machine_numbers+1)
cols = ['Mon_1','Mon_2','Tue_1','Tue_2','Wed_1','Wed_2','Thu_1','Thu_2','Fri_1','Fri_2','Sat_1','Sat_2','Sun_1','Sun_2']
work_df = pd.DataFrame(index=machines,columns=cols)
# 可用工号、姓名列判定
data = pd.read_excel(self.excel_path)
id_list=data[self.judge_col]
# 所有姓名
available_names = id_list.values.tolist() # 初始化,用于第一次判定
iter_count = 0 # 记录执行了多少次,后面要用
n = 0 # 记录多少个被抽到较少的,提前安排上班
for c in cols:
iter_count+=1
if iter_count>1: # 第二轮循环开始要先重置该list,不然减没了
all_names = id_list.values.tolist() # 所有员工名单
available_names = Gaoshiqing.renew(dogs,all_names) # 返回去除刚上完班的
if iter_count==3:
"""
在第三轮,就把每抽到的立马先安排上班
指定第三轮,是怕出现极特殊情况,很多轮就是抽不中他
eager_names 即优先安排上班的员工
"""
names,times = self.get_work_times(work_df)
# 差集,挑出两轮没被抽到的
eager_names = list(set(all_names)-set(names))
n = len(eager_names)
if iter_count>3:
"""
从第四轮开始,时刻注意将被抽到较少的提前排班,
经过三次排班,所有人至少被抽到一次
"""
names,times = self.get_work_times(work_df)
# 找到被抽到次数最少的,可能会>10
less_names = [names for names, x in zip(names, times) if x == min(times)]
# 求交集,刚下班的不可能让其上班
eager_names = list(set(less_names)&(set(available_names)))
n = len(eager_names)
if self.machine_numbers>n>0:
# 先安排上班少的
dogs = sample(eager_names,n)
# 再安排可上班的,挑满
temp = sample(list(set(available_names)-(set(dogs))),self.machine_numbers-n)
dogs.extend(temp)
work_df.loc[:,c] = dogs
if n>=self.machine_numbers:
dogs = sample(eager_names,self.machine_numbers)
work_df.loc[:,c] = dogs
if n ==0:
# 基本是第一、二次才会执行
dogs = sample(available_names,self.machine_numbers)
work_df.loc[:,c] = dogs
return work_df
我们用230人,100台机器来测试下:
循环一百次后,人多了,差异会变大:
改进1:看来还是要考虑已经上过多少次班,比如将本月、本季度上过多少次班的,统计一下,先将数量较少的优先排,即方法中的eager_names,要加一点条件;
改进2:白班、夜班的不同,讲道理每个人白班、夜班要稍微分一分,不能一直上夜班或者上白班,由于我们富裕的人并不多,所以一旦某A周一上白班,某A这周上白班的次数必然较多,可以再排周一白班的时候,加入判断条件;
思考1:正常来讲,每周至少得放一天假吧,所以我们的假设不一定完全成立;
思考2:如果要插空放假,把每个人在每周挑一天放假,可用正则表达式选定周几,在available_names里面调整,由于脑子抽风,写的是血汗工厂的排班情况,就不能再往下了,指不定哪个人就抄袭用上了,因为上面的代码,要实用还需要改。
思考3:比如一台机器要1个人操作,一台机器不停一天要两个班次,一个人不能连续上班,每个人每周至少要放一天假(当天不可上白、夜班),则比如m=10台机器,一周需要10*2*7=140班次,n至少为24人,基于人要请假、休息、生病等因素,实际人数肯定得比这多;
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)