【问题标题】:How can I go through all the formulas and array formulas of a worksheet without repeating each array formula many times?如何在不重复每个数组公式多次的情况下浏览工作表的所有公式和数组公式?
【发布时间】:2013-07-13 17:47:02
【问题描述】:

我想编写一个 VBA 函数,它输出工作表的所有单个公式和数组公式的列表。我想要一个范围的数组公式只打印一次。

如果我按如下方式遍历所有UsedRange.Cells,它将多次打印每个数组公式,因为它涵盖了几个单元格,这不是我想要的。

 For Each Cell In CurrentSheet.UsedRange.Cells
     If Cell.HasArray Then
        St = Range(" & Cell.CurrentArray.Address & ").FormulaArray = " _
                & Chr(34) & Cell.Formula & Chr(34)
     ElseIf Cell.HasFormula Then
        St = Range(" & Cell.Address & ").FormulaR1C1 = " _
                & Chr(34) & Cell.Formula & Chr(34)
     End If
     Print #1, St
 Next

有人有避免这种情况的好主意吗?

【问题讨论】:

  • 如果您查看我的个人资料,您会看到我的Mappit addin。此插件生成每张纸上所有唯一公式的列表 - 以及地图 - 识别唯一公式

标签: vba excel


【解决方案1】:

您基本上需要跟踪您已经看到的内容。最简单的方法是使用 Excel 提供的 UnionIntersect 方法,以及 RangeCurrentArray 属性。

我只是输入了这个,所以我并没有声称它是详尽无遗或没有错误的,但它展示了基本思想:

Public Sub debugPrintFormulas()
    Dim checked As Range

    Dim c As Range
    For Each c In Application.ActiveSheet.UsedRange
        If Not alreadyChecked_(checked, c) Then
            If c.HasArray Then
                Debug.Print c.CurrentArray.Address, c.FormulaArray

                Set checked = accumCheckedCells_(checked, c.CurrentArray)
            ElseIf c.HasFormula Then
                Debug.Print c.Address, c.Formula

                Set checked = accumCheckedCells_(checked, c)
            End If
        End If
    Next c
End Sub

Private Function alreadyChecked_(checked As Range, toCheck As Range) As Boolean
    If checked Is Nothing Then
        alreadyChecked_ = False
    Else
        alreadyChecked_ = Not (Application.Intersect(checked, toCheck) Is Nothing)
    End If
End Function

Private Function accumCheckedCells_(checked As Range, toCheck As Range) As Range
    If checked Is Nothing Then
        Set accumCheckedCells_ = toCheck
    Else
        Set accumCheckedCells_ = Application.Union(checked, toCheck)
    End If
End Function

【讨论】:

  • 我正要写一篇关于字典效率更高的评论……但我收回了。很聪明
  • @Pynner,谢谢。对于我的用例,性能并不重要。在将公式写入文本文件以进行版本控制时,我只做过这种事情,因此假设任何合理的算法,处理工作表当然不会成为瓶颈。但是,虽然我不确定,但我怀疑Range 在幕后实际上是相当高效的。 (我想您可以通过仅累积数组公式范围来稍微优化我的示例。并且,正如@Andy G 所指出的,仅查看带有公式的单元格。)
【解决方案2】:

以下代码产生如下输出:

$B$7 -> =SUM(B3:B6)
$B$10 -> =AVERAGE(B3:B6)
$D$10:$D$13 -> =D5:D8
$F$14:$I$14 -> =TRANSPOSE(D5:D8)

我正在使用一个集合,但它也可以是一个字符串。

Sub GetFormulas()
    Dim ws As Worksheet
    Dim coll As New Collection
    Dim rngFormulas As Range
    Dim rng As Range
    Dim iter As Variant

    Set ws = ActiveSheet
    On Error Resume Next
    Set rngFormulas = ws.Range("A1").SpecialCells(xlCellTypeFormulas)
    If rngFormulas Is Nothing Then Exit Sub 'no formulas
    For Each rng In rngFormulas
        If rng.HasArray Then
            If rng.CurrentArray.Range("A1").Address = rng.Address Then
                coll.Add rng.CurrentArray.Address & " -> " & _
                    rng.Formula, rng.CurrentArray.Address
            End If
        Else
            coll.Add rng.Address & " -> " & _
                rng.Formula, rng.Address
        End If
    Next rng
    For Each iter In coll
        Debug.Print iter
        'or Print #1, iter
    Next iter
    On Error GoTo 0     'turn on error handling
End Sub

主要区别在于,如果正在检查的当前单元格是 CurrentArray 中的单元格 A1,我只会将数组公式写入集合;也就是说,只有当它是数组范围的第一个单元格时。

另一个区别是,我只查看包含使用 SpecialCells 的公式的单元格,这将比检查 UsedRange 更有效。

【讨论】:

  • 我从一个集合开始,因为我希望不添加已经在集合中的项目,但事实证明这有点棘手。另一种方法是使用字典。
  • 奇怪的是,当工作表中没有公式时,ws.Cells.SpecialCells(xlCellTypeFormulas) 会引发错误...
  • @SoftTimur 是的,On Error Resume Next 在 SpecialCells 失败时不起作用。我正在研究解决这个问题的方法。
  • 实际上,On Error Resume Next 确实有效,只是我设置了“所有错误中断”。我已将错误检查添加到我的答案中。
  • 您的第二个版本“高效”。它假设公式对于Range 区域是唯一的,但通常情况并非如此。例如,将两个不同的公式放在相邻的单元格中,如下所示:A1: =42A2: =99。输出将是“$A$1:$A$2 -> =42”。除了迭代每个具有公式的单元格之外,我没有看到任何选择,就像您在第一个版本中所做的那样。
【解决方案3】:

对于您的问题,我看到的唯一可靠的解决方案是将每个新公式与已经考虑过的公式进行交叉检查,以确保没有重复。根据信息量和速度预期,您应该采用不同的方法。

如果大小不是很重要(预计记录数低于 1000),您应该依赖数组,因为它是最快的选择,而且它的实现非常简单。示例:

Dim stored(1000) As String
Dim storedCount As Integer

Sub Inspect()

 Open "temp.txt" For Output As 1
 For Each Cell In CurrentSheet.UsedRange.Cells
     If Cell.HasArray Then
        St = Range(" & Cell.CurrentArray.Address & ").FormulaArray = " _
                & Chr(34) & Cell.Formula & Chr(34)
     ElseIf Cell.HasFormula Then
        St = Range(" & Cell.Address & ").FormulaR1C1 = " _
                & Chr(34) & Cell.Formula & Chr(34)
     End If
     If(Not alreadyAccounted(St) And storedCount <= 1000) Then
        storedCount = storedCount + 1
        stored(storedCount) = St
        Print #1, St
     End If
 Next
 Close 1
End Sub

Function alreadyAccounted(curString As String) As Boolean
    Dim count As Integer: count = 0

    Do While (count < storedCount)
        count = count + 1
        If (LCase(curString) = LCase(stored(count))) Then
            alreadyAccounted = True
            Exit Function
        End If
    Loop
End Function

如果预期的记录数要大得多,我将依赖文件存储/检查。依靠 Excel(将检查的单元格与新范围相关联并在其中查找匹配项)会更容易但速度较慢(主要是在单元格数量很大的情况下)。因此,一种可靠且足够快速的方法(尽管比上述数组慢得多)是从alreadyAccounted 读取您正在创建的文件(我猜是 .txt 文件)。

【讨论】:

    猜你喜欢
    • 2020-08-27
    • 1970-01-01
    • 2011-08-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多