直到在 for 循环中向后退步?

Do until inside a for loop stepping backwards?

提问人:Stuart_P 提问时间:11/15/2023 最后编辑:braXStuart_P 更新时间:11/15/2023 访问量:82

问:

我正在尝试为电子表格编写一些代码,以计算物料清单 (BoM) 中的项目是否是购买的装配的子项。我会尝试更详细地解释。

基本上我的电子表格有很多列,但只需要 2 列就可以计算。为简单起见,我只说这些是 A 列和 B 列,VBA 的输出放在 C 列中。

A 列包含 BoM 级别 - 这始终从级别 0 开始,此级别的子级是级别 1,级别 1 的子级是级别 2,依此类推。目前,在我创建的所有 BoM 中,10 级是“最深”的,但并非不可能增加。

B 列包含程序集状态 - 我对此列唯一感兴趣的值是“P”表示已购买。

由于我的电子表格中的各种标题,数据从第 12 行开始,由于这是顶级,我始终可以在 C 列中将其标记为“TL”。

现在我正在挣扎。从 13 行开始,每行(这很容易扩展到 100,000 行甚至更多!我需要查看 A 列(BoM 级别),然后向上查找下面的级别,直到找到已购买的级别(B 列中的“P”)或达到 BoM 级别 0。

希望这张表会有所帮助

A列 B列 c列
0 免疫垫 TL系列
1 免疫垫
2
2
1 P
2 P
3
3

在考虑如何自动执行此问题时,我想我可以使用带有嵌套 Select Case 的 For 循环,然后是嵌套的 For 循环(使用 Step -1)。

第一个 For 循环从顶部开始,向下查看最后一行,在每一行中查看 BoM 级别,并根据此值使用 Select Case 运行 For 循环(步骤 -1)以开始向上查找行。

我能看到的问题是,我需要为每个可能的 BoM 级别创建一个案例(最大可能的总级别为 99)

到目前为止的代码摘录(仅转到案例 3):

Sub Purch_Child_Formula()   'ADD CHILD OF PURCHASED ASSY FORMULA
Dim AssyStatus As Double
Dim ChildPurchStatus As Double
Dim CurrentBomLevel As Integer
Dim X As Long

Last_Row = ActiveSheet.Range("A" & rows.Count).End(xlUp).Row

For ChildPurchStatus = 13 To Last_Row
    CurrentBomLevel = Cells(ChildPurchStatus, 1).Value
    
        Select Case CurrentBomLevel
        
        Case 1
            For X = ChildPurchStatus To 13 Step -1
            If Cells(X - 1, 1).Value = CurrentBomLevel Then
            Exit For
            ElseIf Cells(X - 1, 1).Value = (CurrentBomLevel - 1) And Cells(X - 1, 2).Value = "P" Then
                Cells(X, 3).Value = "TRUE"
            Else
                Cells(X, 3).Value = "FALSE"
            End If
            Next X
        Case 2
            For X = ChildPurchStatus To 13 Step -1
            If Cells(X - 1, 1).Value = CurrentBomLevel Then
            Exit For
            ElseIf Cells(X - 1, 1).Value = (CurrentBomLevel - 1) And Cells(X - 1, 2).Value = "P" Then
                Cells(X, 3).Value = "TRUE"
            ElseIf Cells(X - 1, 1).Value = (CurrentBomLevel - 2) And Cells(X - 1, 2).Value = "P" Then
                Cells(X, 3).Value = "TRUE"
            Else
                Cells(X, 3).Value = "FALSE"
            End If
            Next X
        Case 3
            For X = ChildPurchStatus To 13 Step -1
            If Cells(X - 1, 1).Value = CurrentBomLevel Then
            Exit For
            ElseIf Cells(X - 1, 1).Value = (CurrentBomLevel - 1) And Cells(X - 1, 2).Value = "P" Then
                Cells(X, 3).Value = "TRUE"
            ElseIf Cells(X - 1, 1).Value = (CurrentBomLevel - 2) And Cells(X - 1, 2).Value = "P" Then
                Cells(X, 3).Value = "TRUE"
            ElseIf Cells(X - 1, 1).Value = (CurrentBomLevel - 3) And Cells(X - 1, 2).Value = "P" Then
                Cells(X, 3).Value = "TRUE"
            Else
                Cells(X, 3).Value = "FALSE"
            End If
            Next X
        Case 4
            
        Case 5
            
        Case 6
            
        Case 7
            
        Case 8
            
        Case 9
            
        Case 10
        
        End Select
            
Next ChildPurchStatus
    
End Sub

这开始起作用,但在达到 BoM 级别 4 之前确实出错了。

所以现在我在想,也许我应该使用一个首字母 for 循环来向下行,然后是另一个 for 循环来查找数据,但这个 for 循环应该继续,直到它在第 2 列中找到条件“P”——我们已经到达了一个 Until 循环。这可能吗?(很明显,编码不是我正常工作的一部分!

对不起,这篇非常冗长的帖子,但我的情况似乎是独一无二的,因为到目前为止,一些在线搜索并没有帮助。

希望这篇文章中有足够的信息。我将添加要求的任何其他详细信息。

Excel VBA for-loop until-loop

评论

0赞 taller 11/15/2023
在列 C 中填充 TRUE 或 FALSE 的逻辑是什么?
0赞 Notus_Panda 11/15/2023
当前行和 BoM 级别 0 之间是否有任何 P?因为这样你就可以保留一个布尔值(找到 P),它会随着每个 BoM 重置,然后检查它是否正确。但是然后从 13 到最后一排。如果 Ax 小于 Ax-1,您也可以重置,例如,如果有必要,因为您提到了“下面的级别”
0赞 Stuart_P 11/15/2023
@taller 道歉 - 我知道我会错过一些东西。如果上述任何 BoM 级别在列 B 中具有“P”,则需要在列 C 中输入 TRUE,令人困惑的是,上面的 BoM 级别是 (BoM 级别 - 1)。如果没有以上级别,则应在列 C 中输入 FALSE。在我上面的表格中,底部的 3 级物品是 TRUE,因为它们上面的级别(2 级)在 col B 中有一个“P”,同样,2 级也是 TRUE 的,因为上面的级别也是购买的(“P”是 col B)。但是,高于此值的级别是 FALSE,因为高于此值的级别在列 C 中不包含“P”
0赞 Stuart_P 11/15/2023
@Notus_Panda 你对布尔的建议看起来很有希望。但是,“P”只能处于高于(或更准确地说低于)当前 BoM 水平的水平。我的示例没有显示它,但在某些情况下,可以在当前行上方找到“P”,但 BoM 级别将等于或大于当前行的 BoM 级别。
0赞 Stuart_P 11/15/2023
试着举一个更好的例子。如果我正在评估数据中的一行,其中列 A 中的级别为“4”。我需要回头查看列 A 中的单元格以找到下一个“3”,看看列 B 是否包含“P”,如果没有,我需要寻找下一个“2”,如果这在列 B 中没有包含“P”,我将需要寻找下一个“1”。如果“0”在列 B 中不包含“P”,我可以在列 C 中输入 FALSE。

答:

0赞 taller 11/15/2023 #1
  • 使用 object 存储上层 BoM 的状态。Dictionary
Option Explicit
Sub Demo()
    Dim i As Long, j As Long
    Dim Sht1 As Worksheet
    Dim arrRes, rngRes As Range
    Dim objDic As Object, sKey As Long
    Set Sht1 = Sheets("Sheet1") ' Modify as needed
    Set rngRes = Sht1.Range("A1").CurrentRegion ' Modify as needed
    arrRes = rngRes.Value
    Set objDic = CreateObject("scripting.dictionary")
    ' Loop through data
    For i = LBound(arrRes) To UBound(arrRes)
        sKey = CLng(arrRes(i, 1))
        If Not objDic.exists(sKey) Or UCase(arrRes(i, 2)) = "P" Then
            objDic(sKey) = UCase(arrRes(i, 2))
        End If
        If sKey > 0 Then
            arrRes(i, 3) = False
            ' Loop through up BoM
            For j = sKey - 1 To 0 Step -1
                If objDic(j) = "P" Then
                    arrRes(i, 3) = True
                    Exit For
                End If
            Next
        End If
    Next i
    ' Update data on sheet
    rngRes.Value = arrRes
End Sub
3赞 Enigmativity 11/15/2023 #2

你可以用一个公式来做到这一点。

=COUNTIFS(RC3:OFFSET(RC1,MAX(FILTER(ROW(R1C1:RC1),R1C1:RC1=0))-ROW(RC),2),"=P")>0

spreadsheet

在 A1 模式下,我的单元格有这个:D1

=COUNTIFS($C1:OFFSET($A1,MAX(FILTER(ROW($A$1:$A1),$A$1:$A1=0))-ROW(D1),2),"=P")>0

评论

0赞 Notus_Panda 11/15/2023
几乎正确,在他的示例中,带有 P 的 1 没有 TRUE。OP 也这样提到: 但是,“P”只能处于高于(或更准确地说低于)当前 BoM 水平的水平
2赞 Notus_Panda 11/15/2023 #3

布尔解决方案

如前所述,我使用布尔值并跟踪哪个 BoM lvl 最低,我还有一个额外的变量:

Sub Purch_Child_Formula()   'ADD CHILD OF PURCHASED ASSY FORMULA
    Dim lRow As Long, i As Long, bomLvl As Long
    Dim ws As Worksheet
    Dim pCheck As Boolean
    
    Set ws = ActiveSheet
    lRow = ws.Range("A" & ws.Rows.Count).End(xlUp).Row
    
    If lRow < 13 Then
        MsgBox "Not enough rows to check"
        Exit Sub
    End If
    
    Dim arr, arrPr
    arr = ws.Range("A13:B" & lRow).Value 'from your example
    ReDim arrPr(1 To UBound(arr, 1), 1 To 1)
    
    For i = 1 To UBound(arr, 1)
        If arr(i, 1) = 0 Then 'resetting
            pCheck = False
            bomLvl = 0
        End If
        If arr(i, 2) = "P" Then
            If Not pCheck Then
                pCheck = True
                bomLvl = arr(i, 1)
            End If
            If bomLvl > arr(i, 1) Then bomLvl = arr(i, 1)
        End If
        arrPr(i, 1) = pCheck And bomLvl < arr(i, 1)
        'there is a P and its BoM is lower than current one ... or not
    Next i
    
    ws.Range("C13:C" & lRow).Value = arrPr 'print everything at once
End Sub

如果以下结果错误,请告诉我:

  • 0 重置所有内容
  • lvl 3 有 P -> lvl 4 及以上为 True
  • lvl 2 有 P,所以现在 lvl 3 及以上是 True
    enter image description here

评论

0赞 Stuart_P 11/16/2023
谢谢你。有趣的是,一位同事(我不知道他涉足了 VBA)建议使用布尔值,它很有效。它不仅可以在不达到 32 位 Excel 的资源限制的情况下运行,而且处理 ~96000 行数据集的时间从大约 5 小时减少到 6 分 18 秒。我对此感到非常高兴!我明天将为任何感兴趣的人添加代码,因为它可能会帮助其他有类似问题的人。
0赞 Notus_Panda 11/16/2023
我的代码在一秒钟内运行了 400k+ 行,所以我有兴趣看看是什么导致了您这边的时间增加。我的猜测是使用 cells()。值(与 Excel 交互)而不是数组(在内存中)