Решил проверить, какой способ из 3ёх (поиск в массиве, метод Range.Find и простой перебор) является оптимальным по соотношению скорость/удобство/универсальность Скорость рассматривается в первую очередь, удобство - во вторую и универсальность - просто для случая "при прочих равных" (просто потому что можно написать код, который будет применять самый быстрый способ в зависимости от задачи, тем самым становясь универсальным)
Перед запуском теста ЗАПОЛНИТЕ таблицу, нажав на кнопку!
Код
Код
Option Explicit
Option Private Module
'====================================================================================================
Const valDef$ = "TestValue"
'====================================================================================================
Sub FillTable()
Dim arr(1 To 1000000, 1 To 2)
Dim r&
For r = 4 To 1000000 Step 4
arr(r - 3, 1) = 1: arr(r - 3, 2) = valDef
arr(r - 2, 1) = 2: arr(r - 2, 2) = valDef
arr(r - 1, 1) = 3: arr(r - 1, 2) = valDef
arr(r, 1) = 4: arr(r, 2) = valDef
Next r
shTest.Cells(2, 1).Resize(UBound(arr, 1), UBound(arr, 2)).Value = arr
End Sub
'====================================================================================================
Sub Start()
Dim x, arrOut(1 To 30, 1 To 4) As Single
Dim t!, p&, f&, r&, c&
t = Timer
For f = 0 To 4
For p = 0 To 5
r = r + 1: Application.StatusBar = "Step: " & r & " out of 30"
x = StandTest(f, p)
For c = 1 To UBound(arrOut, 2)
arrOut(r, c) = Format$(x(c - 1), "0.00")
If arrOut(r, c) = 0 Then arrOut(r, c) = 0.01
Next c
Next p
Next f
Application.StatusBar = False
[_res].Value = arrOut: Calculate
If ActiveSheet.Name <> shComp.Name Then shComp.Select
MsgBox "DONE", vbInformation, Format$(Timer - t, "0.00 sec")
End Sub
'====================================================================================================
Function StandTest(iFilt&, iPos&) As Single() ' iFilt(0-4), iPos(0-5)
Dim rng As Range, cl As Range
Dim arr, valFind, arrTimes(3) As Single, arrOne(1 To 1, 1 To 1)
Dim t!, a&, p&, r&, c&
valFind = "FindMe"
Set rng = [_val]
' choose search position
If iPos = 0 Then GoTo nx1 ' NOTHING to FIND
If iPos = 1 Then p = 1002: GoTo nx1 ' POS at the START
If iPos = 2 Then p = 10002: GoTo nx1 ' POS is near the START
If iPos = 3 Then p = 500002: GoTo nx1 ' POS is in the MIDDLE
If iPos = 4 Then p = 990002: GoTo nx1 ' POS is near the END
If iPos = 5 Then p = 999002: GoTo nx1 ' POS at the END
Err.Raise xlErrNA
nx1:
If p Then shTest.Cells(p, 2).Value = valFind
' фильтр
t = Timer
If iFilt = 0 Then GoTo nx2 ' NO filter
If iFilt = 1 Then Filt (Array("1")): GoTo nx2 ' ONE value
If iFilt = 2 Then Filt (Array("1", "2")): GoTo nx2 ' THREE
If iFilt = 3 Then Filt (Array("1", "2", "3")): GoTo nx2 ' TWO separate
If iFilt = 4 Then Filt (Array("1", "3")): GoTo nx2 ' ONE value, TWO near, THREE, TWO separate)
Err.Raise xlErrNA
nx2:
Set rng = rng.SpecialCells(xlCellTypeVisible)
shTest.Cells(1, 1).AutoFilter
arrTimes(0) = Timer - t ' время ФИЛЬТРА
' поиск в массиве
t = Timer
For a = 1 To rng.Areas.Count
arr = rng.Areas(a).Value
If Not IsArray(arr) Then arrOne(1, 1) = arr: arr = arrOne
For c = 1 To UBound(arr, 2)
For r = 1 To UBound(arr, 1)
If arr(r, c) = valFind Then
Set cl = rng.Areas(a).Cells(r, c): GoTo EX1 ' время МАССИВА
End If
Next r
Next c
Next a
EX1: arrTimes(1) = Timer - t
' поиск методом Range.Find
t = Timer
If rng(1).Value = valFind Then Set cl = rng(1): GoTo EX2
Set cl = rng.Find(valFind, , xlValues, xlWhole, xlByRows, xlNext, False, False, False)
EX2: arrTimes(2) = Timer - t ' время МЕТОДА
' поиск перебором
t = Timer
For Each cl In rng
If cl.Value = valFind Then GoTo EX3
Next cl
Set cl = Nothing
EX3: arrTimes(3) = Timer - t ' время ПЕРЕБОРА
If p Then shTest.Cells(p, 2).Value = valDef
If iPos And cl Is Nothing Then Err.Raise xlErrNA
StandTest = arrTimes
End Function
'====================================================================================================
Sub Filt(arrFilt)
shTest.ListObjects(1).Range.AutoFilter Field:=1, Criteria1:=arrFilt, Operator:=xlFilterValues
End Sub
'====================================================================================================
'====================================================================================================
Sub Pause(Optional iSec! = 1)
Dim iEnd!
iEnd = Timer + iSec: DoEvents
Do While Timer < iEnd: Loop
End Sub
'====================================================================================================
Отчёт
Расширенное сравнение
1. Убран метод - он явно проигрывает и получает дисквалификацию 2. Убран тест, где искомого значения нет в диапазоне — нужен он был, чтобы проверить метод на предмет чудесного определения отсутствия значения без цикла с начала и до конца. Время работы показало, что чуда не произошло
Выводы
Идеального способа, к сожалению, не нашлось: 1. Диапазон состоит из 1 области или в его областях много (например, больше 10ти) ячеек — массив 2. Много отдельных ячеек или областей по 2-3 ячейки — прямой перебор 3. Найти что-то на листе (диапазон не определён или просто огромен) — перебор или метод
Секрет скорости метода при поиске на листе (целиком) в том, что внутри него происходит усечение всей области листа до .UsedRange + .SpecialCells (только видимые + значения + формулы) — то есть метод сам отсекает очевидно ненужное, а дальше ищет старым добрым циклом Забавно смотреть, что, если на новом листе искать 10 в A1 000 000, то это 0 по таймеру Если весь столбец заполнить другими данными, то это 0,5 сек Если очистить, но не переопределять рабочую область, а сразу искать на пустом листе 10 в A1 000 000, то это 0,4 по таймеру — почти также, как с данными (погрешность) и вывод очевиден
Код
Option Explicit
'====================================================================================================
Sub t()
Dim rng As Range, cl As Range
Dim t!
Set rng = Columns(1)
rng.ClearContents
'rng.Value = "TestValue"
rng.Cells(1000000, 1).Value = "=10"
t = Timer
Set cl = rng.Find(10, , xlValues, xlWhole, xlByRows, xlNext, True, False, False)
Debug.Print Fix(1000 * (Timer - t))
MsgBox cl.Address
End Sub
'====================================================================================================
Во всех делах очень полезно периодически ставить знак вопроса к тому, что вы с давних пор считали не требующим доказательств (Бертран Рассел) ►Благодарности сюда◄
Jack Famous написал: Пока только можно точно сказать, что массив стабильно выигрывает у метода
не горячись. Все зависит от обстоятельств. Может быть ситуация, при которой Find Уже нашел, а в массив все еще с листа присваивается. лист имеет с 1 по на весь столбец и вот что имеем
Код
Sub test()
t = Timer
Set a = Columns(1).Find(What:=10, LookIn:=xlValues)
Debug.Print Format(Timer - t, "0.0000")
t = Timer
Z = Columns(1)
Debug.Print Format(Timer - t, "0.0000")
End Sub
0,0039 0,1953 если искать 1000000 то 1,0078 0,1641
БМВ: Все зависит от обстоятельств. Может быть ситуация
формально да - НЕ всегда. Скажу больше: когда мне нужно найти на листе с большим количеством данных что-то, то я просто использую кнопку Найти и Заменить, потому что я даже в массив не смогу забрать больше 10млн ячеек (спасибо Excel x32) Я же в этой теме имею ввиду про ситуацию "есть конкретный диапазон с данными и надо проверить, есть ли в нём определённое значение" Готовлю стенд — сегодня будет
Во всех делах очень полезно периодически ставить знак вопроса к тому, что вы с давних пор считали не требующим доказательств (Бертран Рассел) ►Благодарности сюда◄
ну мы потом с Виталей всё нормально разобрали Код и скрин теста из файла выложил, а описание уже завтра — немного неожиданный результат
Во всех делах очень полезно периодически ставить знак вопроса к тому, что вы с давних пор считали не требующим доказательств (Бертран Рассел) ►Благодарности сюда◄
Во всех делах очень полезно периодически ставить знак вопроса к тому, что вы с давних пор считали не требующим доказательств (Бертран Рассел) ►Благодарности сюда◄