【问题标题】:Is there a way evenly assign employees to a large task list while accounting for some criteria conditions?有没有办法在考虑某些标准条件的同时将员工平均分配到大型任务列表中?
【发布时间】:2020-05-18 15:30:06
【问题描述】:

我每月收到大量任务,我想将这些任务分配给 6 个组,以便他们以均匀分布的方式进行评估。每个任务都有一个排名/优先级,所以如果一个小组收到第一名的任务,我不想也给同一个小组前 100 个优先级。我想应用蛇形/之字形分布。

这使我走上了使用公式=MIN(MOD(ROW()-2,12),MOD(-ROW()+1,12)) 的道路。我得到了我正在寻找的分布,尽管在这个阶段我不知道如何解释我需要添加到我的逻辑中的任何标准。

在上图中,我试图将column F 中的组应用到Column DColumn E 显示了 =MOD() 公式的示例,我可以使用查找将 Mod 值 0-5 替换为我的组 1-6。

我遇到障碍的地方是第 21 行,这是我想说明一些标准或例外情况的地方。我添加了一个二进制column A 用于可视化,但本质上,我想说C 列(任务位置)= Loc4 的位置从不将任务分配给Group 4。在我不想将任务分配给位于 Loc 4 的第 4 组的情况下,我希望跳过 Group 4 进行单个分配,直到它可以应用于下一个可能的排名任务。简单的解决方案是在最后删除所有这些事件,但它确实扭曲了我想要的均匀分布。

我尝试将求解器应用于此作业,寻找最低标准偏差,但我的数据点太多。

这导致我使用一些 vba 逻辑写到另一篇文章,我真的很喜欢这个概念,但我不知道如何修改它以解决一些例外情况。 enter link description here

理想情况下,我很想使用创建我的组的单个数组的概念,只要在这个简短的任务列表中满足条件,将每个组应用于任务,将组写入列表,重置并移动到下一个任务子集。因此,每次我选择接下来的 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 的路径。如果您有任何问题,请告诉我。

谢谢!

【问题讨论】:

  • 你可以检查 Solver 和 assignment 模型 - 可能比 vba 更容易...
  • 谢谢!我将再次查看围绕 Solver 的选项并查看一些分配模型。我的数据集有超过 150,000 条记录/任务,所以我假设我有太多变量,但也许我可以将数据分成子集。

标签: excel vba loops excel-formula


【解决方案1】:

此程序在块大小是组计数的倍数时处理一个任务块。我有 SIZE=2 来给出 12 的块大小,因为这比 6 提供了更多解决冲突的机会。它首先将任务分配给 zigzap 模式,然后根据您定义的规则对其进行验证。这些在模块 validLocn() 中。如果验证正常,则该过程将工作表向下移动到下一个块。如果验证失败,则通过交换随机选择的 2 个元素并重试验证来打乱计划。这将继续增加由 MAXTRY 设置的最大数量。如果仍未解决,用户可以选择重试,忽略并继续或中止该过程。我用 150,000 条记录对其进行了测试,用时不到一分钟,但我的测试数据可能无法代表您的真实数据。在 B、C、D 列的数据透视表中的结果显示 Grp4 在 Locn4 的分布均匀且没有任务。

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

【讨论】:

  • 谢谢!抱歉回复慢。期待在周末尝试此代码。感谢您的意见!
  • @intromind 没关系,使用最新版本,因为我已经更正了 shuffle sub 中的一个潜在错误。
  • 太棒了,谢谢!这很好用!我可以将其添加为我的数据库中的表,以保持程序的速度并保持数据输入简单。我也很欣赏 cmets,因此我可以继续前进,因为我需要挑战自己从头开始开发解决方案,而不是将其他解决方案拼凑在一起。
猜你喜欢
  • 2021-09-01
  • 1970-01-01
  • 2013-10-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-08-25
  • 1970-01-01
  • 2021-12-18
相关资源
最近更新 更多