使用 Excel VBA Range.Find 查找包含相似值的一系列一致数据的第一个和最后一个地址并打破 Do 循环?
Using Excel VBA Range.Find to find first and last addresses of a range of congruent data containing similar values and break a Do Loop?
我一直在努力获得一个 Do/While
循环,使用 Range.Find
和 Range.FindNext
方法来打破无限循环。我已经研究了所有这些,发现大量重复的问题普遍询问“我如何停止无限循环?”但是这些并没有回答我正在寻找的特定条件,因为我不想在 .find
函数找到最后一次迭代时简单地停止 Do
循环,而 returns 找到第一次迭代匹配文本的(即使用 Loop While Not testFind is Nothing And nxtAddr <> firstAddr
类型构造)。
让我们看一些数据示例。我正在处理的任何给定文件都有大约 8000+++ 行数据。在文件中,我正在搜索 dates/times,指定日期和特定时间(例如 - “20210715 12” 作为我的查询):
Row 37: 20210715 14:07:21 ---- This row is the last row of a previous set of congruent data.
(Row 37 and all other data above minus header rows are hidden for user's viewing pleasure.)
Row 38: 20210715 12:48:20 ---- This row is the first row of the congruent data - I CARE ABOUT THIS DATA
Row 39: 20210715 12:47:20
Row 40~85: Date stays same, hour stays the same, rest of time increments down
Row 86: 20210715 12:07:15
Row 87: 20210715 12:07:13 ---- This row is the final row of the congruent data - I CARE ABOUT THIS DATA
Row 88: 20210715 11:54:20 ---- This row is the first row of a new set of congruent data.
一个典型的 Do/While
循环可以在多个地方找到,包括 SO 上的重复项。这些示例(例如 ) 不会阻止 .find
或 .findnext
方法循环遍历我范围的整个其余部分 以确保那里是“不再匹配”。
引用 THIS SO article, and 我偶然发现了一个假设,即范围是否可以在没有 Range.Resize
的情况下通过全等数据动态移动(这不仅将范围向下移动行 - 它增加了大小正在测试的电流范围)。 . .
这可行吗?可行吗?我确实 运行 进入了一个非常令人大开眼界的 facepalm 时刻,当时我偶然发现 通过更改 返回匹配相同文本的最后一行=24=] 到 SearchDirection=xlPrevious
并认为“EUREKA!”
但是
这种在 Range.find
方法中使用 xlPrevious
的解决方案仍然强制循环遍历条目下方的所有剩余行,直到找到匹配项!
底线:如上所示,一旦搜索了一段一致的数据并在最后一行结束,而不必遍历剩余的 7900+++ 行数据,是否可以打破循环?
对于那些可能会问的人——我也特别不想使用 For i = rows.count To 1 Step -1
构建答案,同样,我不想遍历所有单元格来查找最后一次迭代!!!
我找到答案了!这样做绝对是可能的,但不使用 .FindNext
作为解决方案。参考我在 SO 上找到的文章,我即兴创作了集体方法,提出了以下解决方案,它完全符合我的要求。
注意:StartTimer
和StopTimer
使用一种方法来计算执行一个功能所花费的时间,并给我输出到秒(不完全准确, 但足够准确) -- 你可以找到这个 here.
的函数
此部分开始查找我的查询的第一次迭代的过程,然后隐藏数据之前的行(为了清晰可见):
'Note - the values of topRow, dateCol, and r were determined earlier in code, r = rows of used data (NOT USING "USEDRANGE").
With Range(Cells(topRow, dateCol), Cells(r, dateCol))
Set test = .Find("20210715 12", LookIn:=xlValues, Lookat:=xlPart, _
searchorder:=xlByColumns, searchdirection:=xlNext, MatchCase:=False)
If Not test Is Nothing Then
'sets the address of cell above first found match to _not_ hide the first matching cell
strtFndCel = test.Offset(-1, 0).Address
'sets the first found cell's address
strtFnd = test.Address
'sets the next cell's address to specify for future search range.
strtFndNxt = test.Offset(1, 0).Address
Debug.Print "The First Cell Address containing my query = " & strtFnd
Set strtRng = Range(Cells(topRow, 1), strtFndCel)
strtRng.Rows.Hidden = True
End If
End With
并且此部分执行 Do
循环而不使用任何 While
或 Until
并停止 .Find
函数循环遍历我的剩余数据单元格文件。
(解决方案)
Dim findrng As Range
'Uses the strtFndNxt value to set the defined range for the next query of congruent data
Set findrng = Range(strtFnd, strtFndNxt)
Call StartTimer
Do
Set test = Nothing
Set test = findrng.Find("20210715 12", LookIn:=xlValues, Lookat:=xlPart, _
searchdirection:=xlNext, MatchCase:=False)
If test Is Nothing Then
Exit Do
Else
'Note - when looping, the "lastFnd" address increments by 2, but will adequately stop
'on the appropriate cell regardless of even or odd number of rows.
lastFnd = test.Address
strtFnd = test.Offset(1, 0).Address
strtFndNxt = test.Offset(2, 0).Address
Debug.Print "The Current Address containing my query = " & lastFnd
Set findrng = Range(strtFnd, strtFndNxt)
End If
Loop
Debug.Print "The Last Address containing my query using breaking loop Method = " & lastFnd
Call StopTimer
现在 - 为了尽可能彻底和公平,我确实测试了问题中列出的 xlPrevious
方法,它确实为包含匹配项的最终单元格生成了相同的地址.
但是
xlPrevious
方法确实需要更长的时间来遍历剩余的 7900+++ 个单元格。在这种情况下,时间花费了大约 2 秒,我发现这仍然令人印象深刻,因此它相对较快,但是,如果数据范围扩大,这将随着时间的推移花费更长的时间,或者,如果给定的范围不仅仅是一个单列,使用此文件中的相同给定数据会花费更长的时间(经过测试发现大约需要 10 秒......匹配单元格地址的相同结果)。
我唯一一次看到解决方案无用的情况是您可能有更多的行,然后是另一个匹配与以前相同的查询的一致数据块,但即使在这种情况下,您也可以放入一部分使搜索查询重新开始的代码,但使用 after
参数指定搜索在最后一个找到的单元格之后开始。在我的数据中,永远不会出现在另一块与查询不匹配的一致数据之后再次找到相同数据的情况。同样,为了公平起见,这里是使用 xlPrevious
通过比较的方法,如
中指定
Call StartTimer
'topRow, dateCol, and r were determined earlier in code, r = rows of used data (NOT USING "USEDRANGE").
With Range(Cells(topRow, dateCol), Cells(r, dateCol))
Set test = .Find("20210715 12", LookIn:=xlValues, Lookat:=xlPart, _
searchdirection:=xlPrevious, MatchCase:=False)
If Not test Is Nothing Then
lstFnd = test.Address
End If
End With
Debug.Print "The Last Address containing my query using xlPrevious Method = " & lstFnd & vbCrLf & "Range Searched is on single column only."
Call StopTimer
'Uses the entire working range to perform the same query
'instead of on just a single column, but searches by columns
'to make the efforts a little more expedient.
Call StartTimer
'workrng was specified in previous code - NOT USING "USEDRANGE" methods.
With workrng
'Note: removing SearchOrder to use default behavior made NO DIFFERENCE in Timing!
Set test = .Find("20210715 12", LookIn:=xlValues, Lookat:=xlPart, SearchOrder:=xlByColumns, _
searchdirection:=xlPrevious, MatchCase:=False)
If Not test Is Nothing Then
lstFnd = test.Address
' Debug.Print strtFnd
' Debug.Print strtFndNxt
End If
End With
Debug.Print "The Last Address containing my query using xlPrevious Method = " & lstFnd & vbCrLf & "Range Searched is entire working range."
Call StopTimer
最后 - 一些结果
这是我在测试中从我的即时窗格中打印出来的内容:
The First Cell Address containing my query = $A
The Starting Time is: 14:51:33
The Current Address containing my query = $A
The Current Address containing my query = $A
.
.
.
The Current Address containing my query = $A
The Current Address containing my query = $A
.
.
.
The Current Address containing my query = $A
The Current Address containing my query = $A
The Last Address containing my query using breaking loop Method = $A
The End Time is: 14:51:33
The time it took to run this process is: 00:00:00
The Starting Time is: 14:51:33
The Last Address containing my query using xlPrevious Method = $A
Range Searched is on single column only.
The End Time is: 14:51:35
The time it took to run this process is: 00:00:02
The Starting Time is: 14:51:35
The Last Address containing my query using xlPrevious Method = $A
Range Searched is entire working range.
The End Time is: 14:51:45
The time it took to run this process is: 00:00:10
我一直在努力获得一个 Do/While
循环,使用 Range.Find
和 Range.FindNext
方法来打破无限循环。我已经研究了所有这些,发现大量重复的问题普遍询问“我如何停止无限循环?”但是这些并没有回答我正在寻找的特定条件,因为我不想在 .find
函数找到最后一次迭代时简单地停止 Do
循环,而 returns 找到第一次迭代匹配文本的(即使用 Loop While Not testFind is Nothing And nxtAddr <> firstAddr
类型构造)。
让我们看一些数据示例。我正在处理的任何给定文件都有大约 8000+++ 行数据。在文件中,我正在搜索 dates/times,指定日期和特定时间(例如 - “20210715 12” 作为我的查询):
Row 37: 20210715 14:07:21 ---- This row is the last row of a previous set of congruent data. (Row 37 and all other data above minus header rows are hidden for user's viewing pleasure.) Row 38: 20210715 12:48:20 ---- This row is the first row of the congruent data - I CARE ABOUT THIS DATA Row 39: 20210715 12:47:20 Row 40~85: Date stays same, hour stays the same, rest of time increments down Row 86: 20210715 12:07:15 Row 87: 20210715 12:07:13 ---- This row is the final row of the congruent data - I CARE ABOUT THIS DATA Row 88: 20210715 11:54:20 ---- This row is the first row of a new set of congruent data.
一个典型的 Do/While
循环可以在多个地方找到,包括 SO 上的重复项。这些示例(例如 .find
或 .findnext
方法循环遍历我范围的整个其余部分 以确保那里是“不再匹配”。
引用 THIS SO article, and Range.Resize
的情况下通过全等数据动态移动(这不仅将范围向下移动行 - 它增加了大小正在测试的电流范围)。 . .
这可行吗?可行吗?我确实 运行 进入了一个非常令人大开眼界的 facepalm 时刻,当时我偶然发现 SearchDirection=xlPrevious
并认为“EUREKA!”
但是
这种在 Range.find
方法中使用 xlPrevious
的解决方案仍然强制循环遍历条目下方的所有剩余行,直到找到匹配项!
底线:如上所示,一旦搜索了一段一致的数据并在最后一行结束,而不必遍历剩余的 7900+++ 行数据,是否可以打破循环?
对于那些可能会问的人——我也特别不想使用 For i = rows.count To 1 Step -1
构建答案,同样,我不想遍历所有单元格来查找最后一次迭代!!!
我找到答案了!这样做绝对是可能的,但不使用 .FindNext
作为解决方案。参考我在 SO 上找到的文章,我即兴创作了集体方法,提出了以下解决方案,它完全符合我的要求。
注意:StartTimer
和StopTimer
使用一种方法来计算执行一个功能所花费的时间,并给我输出到秒(不完全准确, 但足够准确) -- 你可以找到这个 here.
此部分开始查找我的查询的第一次迭代的过程,然后隐藏数据之前的行(为了清晰可见):
'Note - the values of topRow, dateCol, and r were determined earlier in code, r = rows of used data (NOT USING "USEDRANGE").
With Range(Cells(topRow, dateCol), Cells(r, dateCol))
Set test = .Find("20210715 12", LookIn:=xlValues, Lookat:=xlPart, _
searchorder:=xlByColumns, searchdirection:=xlNext, MatchCase:=False)
If Not test Is Nothing Then
'sets the address of cell above first found match to _not_ hide the first matching cell
strtFndCel = test.Offset(-1, 0).Address
'sets the first found cell's address
strtFnd = test.Address
'sets the next cell's address to specify for future search range.
strtFndNxt = test.Offset(1, 0).Address
Debug.Print "The First Cell Address containing my query = " & strtFnd
Set strtRng = Range(Cells(topRow, 1), strtFndCel)
strtRng.Rows.Hidden = True
End If
End With
并且此部分执行 Do
循环而不使用任何 While
或 Until
并停止 .Find
函数循环遍历我的剩余数据单元格文件。
(解决方案)
Dim findrng As Range
'Uses the strtFndNxt value to set the defined range for the next query of congruent data
Set findrng = Range(strtFnd, strtFndNxt)
Call StartTimer
Do
Set test = Nothing
Set test = findrng.Find("20210715 12", LookIn:=xlValues, Lookat:=xlPart, _
searchdirection:=xlNext, MatchCase:=False)
If test Is Nothing Then
Exit Do
Else
'Note - when looping, the "lastFnd" address increments by 2, but will adequately stop
'on the appropriate cell regardless of even or odd number of rows.
lastFnd = test.Address
strtFnd = test.Offset(1, 0).Address
strtFndNxt = test.Offset(2, 0).Address
Debug.Print "The Current Address containing my query = " & lastFnd
Set findrng = Range(strtFnd, strtFndNxt)
End If
Loop
Debug.Print "The Last Address containing my query using breaking loop Method = " & lastFnd
Call StopTimer
现在 - 为了尽可能彻底和公平,我确实测试了问题中列出的 xlPrevious
方法,它确实为包含匹配项的最终单元格生成了相同的地址.
但是
xlPrevious
方法确实需要更长的时间来遍历剩余的 7900+++ 个单元格。在这种情况下,时间花费了大约 2 秒,我发现这仍然令人印象深刻,因此它相对较快,但是,如果数据范围扩大,这将随着时间的推移花费更长的时间,或者,如果给定的范围不仅仅是一个单列,使用此文件中的相同给定数据会花费更长的时间(经过测试发现大约需要 10 秒......匹配单元格地址的相同结果)。
我唯一一次看到解决方案无用的情况是您可能有更多的行,然后是另一个匹配与以前相同的查询的一致数据块,但即使在这种情况下,您也可以放入一部分使搜索查询重新开始的代码,但使用 after
参数指定搜索在最后一个找到的单元格之后开始。在我的数据中,永远不会出现在另一块与查询不匹配的一致数据之后再次找到相同数据的情况。同样,为了公平起见,这里是使用 xlPrevious
通过比较的方法,如
Call StartTimer
'topRow, dateCol, and r were determined earlier in code, r = rows of used data (NOT USING "USEDRANGE").
With Range(Cells(topRow, dateCol), Cells(r, dateCol))
Set test = .Find("20210715 12", LookIn:=xlValues, Lookat:=xlPart, _
searchdirection:=xlPrevious, MatchCase:=False)
If Not test Is Nothing Then
lstFnd = test.Address
End If
End With
Debug.Print "The Last Address containing my query using xlPrevious Method = " & lstFnd & vbCrLf & "Range Searched is on single column only."
Call StopTimer
'Uses the entire working range to perform the same query
'instead of on just a single column, but searches by columns
'to make the efforts a little more expedient.
Call StartTimer
'workrng was specified in previous code - NOT USING "USEDRANGE" methods.
With workrng
'Note: removing SearchOrder to use default behavior made NO DIFFERENCE in Timing!
Set test = .Find("20210715 12", LookIn:=xlValues, Lookat:=xlPart, SearchOrder:=xlByColumns, _
searchdirection:=xlPrevious, MatchCase:=False)
If Not test Is Nothing Then
lstFnd = test.Address
' Debug.Print strtFnd
' Debug.Print strtFndNxt
End If
End With
Debug.Print "The Last Address containing my query using xlPrevious Method = " & lstFnd & vbCrLf & "Range Searched is entire working range."
Call StopTimer
最后 - 一些结果
这是我在测试中从我的即时窗格中打印出来的内容:
The First Cell Address containing my query = $A The Starting Time is: 14:51:33 The Current Address containing my query = $A The Current Address containing my query = $A . . . The Current Address containing my query = $A The Current Address containing my query = $A . . . The Current Address containing my query = $A The Current Address containing my query = $A The Last Address containing my query using breaking loop Method = $A The End Time is: 14:51:33 The time it took to run this process is: 00:00:00 The Starting Time is: 14:51:33 The Last Address containing my query using xlPrevious Method = $A Range Searched is on single column only. The End Time is: 14:51:35 The time it took to run this process is: 00:00:02 The Starting Time is: 14:51:35 The Last Address containing my query using xlPrevious Method = $A Range Searched is entire working range. The End Time is: 14:51:45 The time it took to run this process is: 00:00:10