excel - 有没有办法在考虑某些标准条件的同时将员工平均分配到大型任务列表中?
问题描述
我有一个每月收到的动态任务群,我想将这些任务分配给 6 个组,以便他们以均匀分布的方式进行评估。每个任务都有一个排名/优先级,所以如果一个小组收到第一名的任务,我不想也给同一个小组前 100 个优先级。我想应用蛇形/之字形分布。
这使我走上了使用公式的道路=MIN(MOD(ROW()-2,12),MOD(-ROW()+1,12))
。我得到了我正在寻找的分布,尽管在这个阶段我不知道如何解释我需要添加到我的逻辑中的任何标准。
在上图中,我试图将这些组纳入column F
并将它们应用于Column D
. Column E
显示了=MOD()
公式的示例,我可以使用查找将 Mod 值 0-5 替换为我的组 1-6。
我遇到障碍的地方是第 21 行,这是我想说明一些标准或例外情况的地方。我添加了一个用于可视化的二进制文件column A
,但本质上,我想说的是 C 列(任务位置)= Loc4
从不将任务分配给Group 4
. 在我不希望将任务分配到 Loc 4 的第 4 组的情况下,我希望跳过Group 4
单个分配,直到它可以应用于下一个可能的排序任务。简单的解决方案是在最后删除所有这些事件,但它确实扭曲了我想要的均匀分布。
我尝试将求解器应用于此作业,寻找最低标准偏差,但我有太多数据点。
这导致我使用一些 vba 逻辑写到另一篇文章,我真的很喜欢这个概念,但我不知道如何修改它以解决一些例外情况。在此处输入链接描述
理想情况下,我很想使用创建我的组的单个数组的概念,只要在这个简短的任务列表中满足标准,将每个组应用于任务,将组写入列表,重置并向下移动到下一个任务子集。因此,每次我选择接下来的 6 个任务时,它们都会分配到我的 6 个组中的一个,这将保持我希望的分布。
这是我试图应用的用户K.Davis帖子的代码:
Sub assignEmployeeTasks()
Dim ws As Worksheet, i As Long
Set ws = ThisWorkbook.Worksheets(1)
Dim employeeList() As Variant
With ws
For i = 2 To lastRow(ws, 2)
If (Not employeeList) = -1 Then
'rebuild employeelist / array uninitialized
employeeList = buildOneDimArr(ws, "F", 2, lastRow(ws, "F"))
End If
.Cells(i, 4) = randomEmployee(employeeList)
Next
End With
End Sub
这些是允许您的程序完成工作的“支持”功能:
Function randomEmployee(ByRef employeeList As Variant) As String
'Random # that will determine the employee chosen
Dim Lotto As Long
Lotto = randomNumber(LBound(employeeList), UBound(employeeList))
randomEmployee = employeeList(Lotto)
'Remove the employee from the original array before returning it to the sub
Dim retArr() As Variant, i&, x&, numRem&
numRem = UBound(employeeList) - 1
If numRem = -1 Then 'array is empty
Erase employeeList
Exit Function
End If
ReDim retArr(numRem)
For i = 0 To UBound(employeeList)
If i <> Lotto Then
retArr(x) = employeeList(i)
x = x + 1
End If
Next i
Erase employeeList
employeeList = retArr
End Function
' This will take your column of employees and place them in a 1-D array
Function buildOneDimArr(ByVal ws As Worksheet, ByVal Col As Variant, _
ByVal rowStart As Long, ByVal rowEnd As Long) As Variant()
Dim numElements As Long, i As Long, x As Long, retArr()
numElements = rowEnd - rowStart
ReDim retArr(numElements)
For i = rowStart To rowEnd
retArr(x) = ws.Cells(i, Col)
x = x + 1
Next i
buildOneDimArr = retArr
End Function
' This outputs a random number so you can randomly assign your employee
Function randomNumber(ByVal lngMin&, ByVal lngMax&) As Long
'Courtesy of https://stackoverflow.com/a/22628599/5781745
Randomize
randomNumber = Int((lngMax - lngMin + 1) * Rnd + lngMin)
End Function
' This gets the last row of any column you specify in the arguments
Function lastRow(ws As Worksheet, Col As Variant) As Long
lastRow = ws.Cells(ws.Rows.Count, Col).End(xlUp).Row
End Function
任何帮助将非常感激!我愿意走任何更接近我想要的解决方案、公式或 vba 的路径。请让我知道,如果你有任何问题。
谢谢!
解决方案
该程序在块大小是组计数的倍数时处理一个任务块。我有 SIZE=2 来给出 12 的块大小,因为这比 6 提供了更多解决冲突的机会。它的工作原理是最初将任务分配给 zigzap 模式,然后根据您定义的规则对其进行验证。这些在模块 validLocn() 中。如果验证正常,则该过程将工作表向下移动到下一个块。如果验证失败,则通过交换随机选择的 2 个元素并重试验证来打乱计划。这将继续增加由 MAXTRY 设置的最大数量。如果仍未解决,用户可以选择重试,忽略并继续或中止该过程。我用 150,000 条记录对其进行了测试,用时不到一分钟,但我的测试数据可能无法代表您的真实数据。结果在列 B、C 上的数据透视表中,
Count of Task
Row Loc1 Loc2 Loc3 Loc4 Loc5 Loc6 Gand Total
Gp1 4013 3975 3926 5082 3986 4018 25000
Gp2 4021 3992 4077 4928 3975 4007 25000
Gp3 3976 3952 4027 5023 4049 3973 25000
Gp4 5050 4915 4936 5035 5064 25000
Gp5 4072 3996 4034 4890 3969 4039 25000
Gp6 3964 4087 3986 5018 3996 3949 25000
Grand 25096 24917 24986 24941 25010 25050 150000
希望有帮助。
Option Explicit
Sub assignEmployeeTasks()
Dim ws As Worksheet, t0 As Single, t1 As Single
Set ws = ThisWorkbook.Sheets("Sheet1")
t0 = Timer
Const COL_GROUP = "F"
Const COL_LOCN = "C"
Const SIZE As Integer = 2 ' plan size = 2 * group count
Const MAXTRY = 50 ' no of tries to validate
Dim bOK As Boolean
Dim grp As Variant, iBlockStart As Long, i As Integer, r As Integer, step As Integer
'initialize grps and location
Dim countGrp As Integer, lastLocn As Long
lastLocn = ws.Range(COL_LOCN & Rows.Count).End(xlUp).Row
countGrp = ws.Range(COL_GROUP & Rows.Count).End(xlUp).Row - 1
grp = ws.Range(COL_GROUP & "2").Resize(countGrp, 1).Value
Dim plan() As String
ReDim plan(countGrp * SIZE, 2)
Dim itry As Integer, res
iBlockStart = 1
Do While iBlockStart < lastLocn
' initialize plan
Call zigzag(plan, grp)
For i = 1 To UBound(plan)
plan(i, 1) = ws.Range("C" & iBlockStart + i).Value
Next
' save 1st attempt
For i = 1 To UBound(plan)
ws.Range("D" & iBlockStart + i).Value = plan(i, 2)
Next
' validate
bOK = validLocn(plan, 0)
retry:
' retry to validate
itry = 0
While bOK = False And itry < MAXTRY
Call shuffle(plan, 1)
bOK = validLocn(plan, itry)
itry = itry + 1
Wend
' write new plan to sheet
For i = 1 To UBound(plan)
ws.Range("D" & iBlockStart + i).Value = plan(i, 2)
Next
' check rule again
If itry = MAXTRY Then
ws.Range(COL_LOCN & iBlockStart).Select
res = MsgBox("Failed to vaidate after " & MAXTRY & " attempts", vbAbortRetryIgnore, iBlockStart)
If res = vbRetry Then GoTo retry
If res = vbAbort Then Exit Sub
End If
iBlockStart = iBlockStart + UBound(plan)
Loop
t1 = Timer
MsgBox "Assigned " & lastLocn - 1 & " tasks in " & Int(t1 - t0) & " secs"
End Sub
' valid plan against rules
Function validLocn(plan As Variant, itry) As Boolean
Dim sLocn As String, sGrp As String, i As Integer
validLocn = True
For i = 1 To UBound(plan)
sLocn = plan(i, 1)
sGrp = plan(i, 2)
' rule 1
If sGrp = "Gp4" And sLocn = "Loc4" Then
validLocn = False
'Debug.Print itry, i, "Fail Rule 1", sGrp, sLocn
Else
'Debug.Print itry, i, "Pass Rule 1", sGrp, sLocn
End If
Next
End Function
' populate plan groups
Sub zigzag(plan As Variant, grp As Variant)
Dim i As Integer, r As Integer, step As Integer
r = 1: step = 1
For i = 1 To UBound(plan)
plan(i, 2) = grp(r, 1)
r = r + step
If r > UBound(grp) Then
r = UBound(grp)
step = -1
ElseIf r < 1 Then
r = 1
step = 1
End If
Next
End Sub
' shuffle plan
Sub shuffle(plan As Variant, i As Integer)
Dim tmp As String, n As Integer, j As Integer, k As Integer
For n = 1 To i
' random choose elements to shuffle
retry:
k = Int(1 + Rnd() * UBound(plan))
j = Int(1 + Rnd() * UBound(plan))
If k = j Then GoTo retry
tmp = plan(k, 2)
plan(k, 2) = plan(j, 2)
plan(j, 2) = tmp
Next
End Sub
' generate test data
Sub testdata()
Dim ws As Worksheet, i As Long
Set ws = ThisWorkbook.Sheets("Sheet1")
For i = 2 To 150001
ws.Cells(i, 2) = i - 1
ws.Cells(i, 3) = "Loc" & 1 + Int(Rnd() * 6)
Next
End Sub
推荐阅读
- ajax - 如何使用ajax调用控制器函数
- ruby-on-rails - 使用 ActiveRecord 在同一个表中创建对象的组合列表
- signalr - SignalR 客户端服务器已连接但未将数据推送到生产服务器上的客户端
- css - Mac 和 Windows 上的“.msg”字体颜色不同
- oracle-adf - 默认值根据从 LOV 中选择的值存储在另一列中
- excel - VBA 宏从 CSV 文件中读取用户选择的字段名称
- java - 如何在使用 Exchange Web Service(JAVA) 发送之前获取电子邮件的消息 ID?
- generics - Dart,不能调用通用的方法
- c# - 如何仅在输入 PIN 时将 Pin 输入 4 位数字以及如何验证?
- ios - UIKIT_DEFINE_AS_PROPERTIES 在哪里定义?