Function 自定义查找函数

Function 自定义查找函数,function,vba,excel,Function,Vba,Excel,我试图创建一个函数,在搜索整个活动工作表后,该函数将返回包含特定字符串的单元格总数。很像Find and Replace中的“x单元格”是如何工作的 到目前为止,我有: Function FINDIST(stringToFind) Dim counter As Integer: counter = 0 For Each Cell In ActiveSheet.UsedRange.Cells If InStr (Cell, stringToFind) > 0 Then counter = c

我试图创建一个函数,在搜索整个活动工作表后,该函数将返回包含特定字符串的单元格总数。很像Find and Replace中的“x单元格”是如何工作的

到目前为止,我有:

Function FINDIST(stringToFind)
Dim counter As Integer: counter = 0
For Each Cell In ActiveSheet.UsedRange.Cells
If InStr (Cell, stringToFind) > 0
Then counter = counter + 1
End If
Next
End Function

按照Tony Dallimore的建议使用Find,并将您的返回类型更改为Long

MSDN文章:

函数FINDIST(stringToFind)的长度
变暗计数器长度:计数器=0
调光范围
将第一个地址设置为字符串
使用ActiveSheet.UsedRange
集合c=.Find(stringToFind,LookIn:=xlValues,LookAt:=xlPart)
如果不是,那么c什么都不是
firstAddress=c.地址
做
计数器=计数器+1
集合c=.FindNext(c)
循环而不是c为Nothing,c.Address为firstAddress
如果结束
以
FINDIST=计数器
端函数

Find通常比编码的等价物更快,但我还没有针对其他任何东西进行过速度测试,如果它是快的还是慢的,我会对这里感兴趣。

另一种方法:

Function FINDIST(stringToFind) As Long
    FINDIST = Evaluate("SUM(IFERROR(SEARCH(" & Chr(34) _
        & "*" & stringToFind & "*" & Chr(34) & "," _
            & ActiveSheet.UsedRange.Address & ",1),0))")
End Function
这将在使用范围内的每个单元格中搜索
stringToFind
,如果在单元格中找到该字符串,则返回一个带1的数组,如果未找到该字符串,则返回错误。使用
IFERROR
部分将错误转换为零,然后
SUM
对生成的二进制数组求和

这将只计算每个单元格中出现的
stringToFind
一次,即使它出现不止一次,但查看您的代码,我假设这就是您要查找的内容

我希望有帮助

更新

出于好奇,我做了一些测试,看看这两种方法是如何比较的(直接从范围读取还是使用evaluate)。以下是我使用的代码:

Option Explicit
Private Declare Function GetTickCount Lib "kernel32.dll" () As Long

Sub test()
Dim ticks As Long
Range("A1:AA100000").Value = "adlrkjgalbabyajglakrjg"

ticks = GetTickCount
FINDIST1 ("baby")
Debug.Print "Read from range: ", GetTickCount - ticks

ticks = GetTickCount
FINDIST ("baby")
Debug.Print "Evaluate: ", GetTickCount - ticks

End Sub

Function FINDIST(stringToFind) As Long
    FINDIST = Evaluate("SUM(IFERROR(SEARCH(" & Chr(34) _
    & "*" & stringToFind & "*" & Chr(34) & "," _
      & ActiveSheet.UsedRange.Address & ",1),0))")
End Function


Function FINDIST1(stringToFind) As Long
Dim counter As Long: counter = 0
Dim c As Range
Dim firstAddress As String

With ActiveSheet.UsedRange
    Set c = .Find(stringToFind, LookIn:=xlValues, LookAt:=xlPart)
    If Not c Is Nothing Then
        firstAddress = c.Address
        Do
            counter = counter + 1
            Set c = .FindNext(c)
        Loop While Not c Is Nothing And c.Address <> firstAddress
    End If
End With

FINDIST1 = counter

End Function
道格·格兰西提出了另一个非常好的观点,即可以使用
COUNTIF
代替
SEARCH
。这将导致一个非数组公式解决方案,并将主导我的原始公式,性能方面

这是道格的公式:

FINDIST_COUNTIF = ActiveSheet.Evaluate("COUNTIF(" _
        & ActiveSheet.Cells.Address & "," & Chr(34) & "*"  _ 
          & stringToFind & "*" & Chr(34) & ")")
事实上,Doug的观点意味着没有必要使用
Evaluate()
。我们可以从
WorksheetFunction
对象调用
Countif
。因此,如果目标是从电子表格调用此函数,则无需使用
Evaluate()
或将其封装在
UDF
中-这是一个典型的带有通配符的
COUNTIF
应用程序

结果:

  Read from range:           247,495 ms (~ 4 mins 7 secs)
  Application.Evaluate:        3,261 ms (~ 3.2 secs)
  Variant Array:               1,706 ms (~ 1.7 secs)
  ActiveSheet.Evaluate:        1,257 ms (~ 1.3 secs)
  ActiveSheet.Evaluate (DG):     602 ms (~ 0.6 secs)
  WorksheetFunction.CountIf (DG):550 ms (~ 0.55 secs)
与使用
Range.Find()
(?!)相比,
Application.Evaluate
的速度大约快75倍。此外,原始代码(将
Integer
更改为
Long
)运行时间约为8秒

另外,在这种特殊情况下,
Activesheet.Evaluate
实际上比
Variant
数组更快。将
CountIf
作为
WorksheetFunction
方法调用与
Evaluate
ing调用之间的区别似乎很小

警告:在
UsedRange
中找到
stringToFind
的频率可能会影响几种方法的相对性能。我运行了
Activesheet.evaluation
Variant Array
方法,使用上述范围
(A1:AA100000)
,但只有前十个单元格具有匹配字符串

结果(平均6次,差异非常小):

这很有趣-在这种情况下,
ActiveSheet.Evaluate
的性能似乎比variant数组稍好一些(除非我在循环代码中做了一些可怕的事情,在这种情况下请让我知道)。另外,
变量
方法的性能实际上是。。相对于字符串的频率不变


运行在
Excel2010
上的
Win7

替换
函数FINDIST(stringToFind)
下,只要
函数FINDIST(stringToFind)长
。将
变暗计数器替换为整数
,将
变暗计数器替换为长
。在结束函数之前添加
FINDDIST=counter
。这将是非常缓慢的
UsedRange
可以轻松地显示整个工作表。在VBA帮助中查找
Find
,其中显示了如何查找字符串的每个匹配项。别忘了包括
LookAt:=xlPart
。好的,你会提出一个有趣的话题。我确实阅读了Find方法文档,但没有看到它返回布尔值。我需要一个用于IF的布尔值。为什么需要布尔值?我假设它是您希望函数返回的匹配计数。只需将
counter=counter+1
包含在
Find
循环中,并返回
counter
@TonyDallimore的最终值,谢谢您为我指明了正确的方向。看看答案和他们的评论,看看我们是如何解决的。
FINDIST_COUNTIF = ActiveSheet.Evaluate("COUNTIF(" _
        & ActiveSheet.Cells.Address & "," & Chr(34) & "*"  _ 
          & stringToFind & "*" & Chr(34) & ")")
  Read from range:           247,495 ms (~ 4 mins 7 secs)
  Application.Evaluate:        3,261 ms (~ 3.2 secs)
  Variant Array:               1,706 ms (~ 1.7 secs)
  ActiveSheet.Evaluate:        1,257 ms (~ 1.3 secs)
  ActiveSheet.Evaluate (DG):     602 ms (~ 0.6 secs)
  WorksheetFunction.CountIf (DG):550 ms (~ 0.55 secs)
  Activesheet.Evaluate:        920 ms (~  1. sec)
  Variant Array:               1654 ms (~ 1.7 secs)