实际的问题来源于LQA系统的人员分配工作量,有两种方式,一种是 平均分配,一种是按给定比例分配。不需要AC,能得到符合题意的解就算达成目标。
一个order订单包含一个xls表格,内含若干词条,需要把词条分配给若干LQA,每个人负责的词条总字数大致相近就好,不必完全一致。
词条不可拆分,分配工作量的占比是按字数占比。
另外延伸一个应用情况:
已知现在有x个小组,(小组之间人数差距比较大,假设有的100人,有的10个人,),需要分成n个部门。要求,小组不能拆分,各个部门的人数较为均衡。求,每个部门的小组。
将一个包含m个整数的数组分成n个数组,每个数组的和尽量接近
input:
# 给定小组及其对应人数
groups = {'a':100, 'b':10, 'c':23, 'd':23, 'e':12, 'f':34, 'g':67, 'h':135, 'i':5, 'j':39, 'k':60, 'l':204}
# 目标部门数
n = 5
output:
groups: [[204], [135], [100, 23], [67, 39, 12, 5], [60, 34, 23, 10]]
这里参照了这个同学的思路
https://cloud.tencent.com/developer/article/1659134
感谢
根据以上思路,核心思想是每一趟遍历一次 数组,每一趟得出一个分组列表,因为需要分为5组,所以进行5趟分组,每一趟从后开始遍历。
遍历的目的是 依次比较每个数value,和剩下的 数的平均值 avg(sum_leftover/(n-i), i 为趟数),
如果 value >=avg,则加入groups,成为一个list,并直接结束循环(因为已经比avg大的话,加上任何数只会偏离avg,增加方差)
如果 value < avg, 则加入groups,成为该趟 list 的第一个成员,继续往后循环,看看是否能加上一个数,使这个分组更接近avg。循环结束后终止这次遍历。
最后一趟,则把剩下所有数字放入最后一个groups 的同一个list。
原文举例:
数组为:500, 18, 28, 2, 27, 35, 22, 10, 6, 5, 3, 2, 1;分为4组
排序为:500, 35, 28, 27, 22, 18, 10, 6, 5, 3, 2, 2, 1
计算平均值 avg = 164.75
遍历数组:
第一轮:500 > avg,取出500单独作为一组;剩余数组为 35, 28, 27, 22, 18, 10, 6, 5, 3, 2, 2, 1
计算avg = 53
第二轮:35 < avg,取出35放入到第二组;
delta=53-35=18;
接下来为28 > 18,继续遍历,27 > 18,22 > 18,18 == 18,于是取出18加入到第二组,结束第二轮,剩余数组为 28, 27, 22, 10, 6, 5, 3, 2, 2, 1
第三轮:28 < avg, 取出28放入到第三组;
delta=53-28=25
27 > delta > 22,27-delta=2,delta-22=3,distance = 2,将22加入临时数组,delta = 3;
18 >3, ... ,5 > 3, 3==3,distance = delta-3 = 0;于是将22和3加入到第三组,结束第三轮,属于数组为 27, 10, 6, 5, 2, 2, 1
第四轮:直接返回剩下数加入到一个组作为第四组
结果:
arr 0 is : 500, sum = 500
arr 1 is : 35 18, sum = 53
arr 2 is : 28 22 3, sum = 53
arr 3 is : 27 10 6 5 2 2 1, sum = 53
大致理解之后我就直接动手做了,没有参照原文的go代码,具体细节的问题没有特别考虑,比如经典 的动态规划一般怎么做,比如这里是否可以不考虑循环中不能remove成员的问题(长度动态变化 的迭代对象不能再次被访问)。
(我在下面的第一次实现中 未在循环中remove,严格遵守了他的语法,而实际上,因为这个for循环里,只会在满足 if 条件的时候才会做remove操作(长度变化),并且remove马上break跳出循环了,不会再进行下一次循环,所以不会报错,符合语法规范,可以大胆remove,可以减少很多不必要的变量,这点我在 按比例分配 时做了)
import numpy as np
def split(groups, n):
# 求总数
# 均分
values = [i for i in groups.values()]
values.sort(reverse=True)
print (values, "length:", len(values))
groups =[]
# values_copy = values.copy()
count_remove = 0
cnt_out = 0
for index in range(n):# 循环组数
print (index)
if index == (n-1):
# 最后一次,直接把剩下的列为一个组
groups_list = np.sum(groups)
# 求两个list的差集
for num in groups_list:
if num in values:
values.remove(num)
groups.append(values)
print ("groups:",groups)
return groups
else:
cnt = 0
for value in values[cnt_out:]:
# 计算剩下的数的均值
avg = (sum(values) -np.sum(np.sum(groups)) )/(n - index)
print ("avg:", avg)
if value >= avg:
print ("value:", value)
count_remove += value
group = [value]
groups.append(group)
break#结束本层循环,继续下一层循环
else:
group = [value]
count_remove += value
delta = avg-value
print ("delta:", delta)
# 从后面的数列中找到 x =dealta
print (cnt)
for i in values[cnt:]:
print ("searching in :", i)
if i <= delta:
group.append(i)
count_remove += i
delta -= i
groups.append(group)
break
cnt +=1
cnt_out +=1
得到结果如上,大致均衡(第一个为204是因为这个204不可再分)
有了前面的经验这个就简单多了,可以说是split方法 变形,在删除了很多无意义变量后,代码如下
前提:在开始之前,先按比例对总人数进行了计算,得到以下标准组:
input:
groups = {'a':100, 'b':10, 'c':23, 'd':23, 'e':12, 'f':34, 'g':67, 'h':135, 'i':5, 'j':39, 'k':60, 'l':204}
percents = [45, 20,20, 15]
n = 4
# 计算后
split_words = [320 , 142, 142, 106]
output:
可以自己先动手笔算一下预期结果
import numpy as np
def split(groups, n, split_words):
values = [i for i in groups.values()]
values.sort(reverse=True)
print (values, "length:", len(values))
groups = []
for index in range(n):
standard = split_words[index]
if index == (n-1):
groups.append(values)
return groups
else:
for value in values:
if value > standard:
group = [value]
values.remove(value)
groups.append(group)
break
else:
group = [value]
values.remove(value)
delta =standard-value
for i in values:
print ("searching in :", i)
if i <= delta:
values.remove(i)
group.append(i)
delta -= i
groups.append(group)
break
TODO:由于业务原因,我暂时没有思考数值重复的问题。
这个看起来比前面那个平均分配的舒服多了,也可以按照这个思路改造一下前面的代码,平均分配的代码也可以采用先算出平均数值的方法,节省后面的计算成本, 欢迎讨论。