Написал небольшую функцию по поиску подстроки в строке. Возможно кому пригодится. Функция расчитана на работу с любыми строками в ASC II кодировке.
Алгоритм и как все это работает: - за основу был опробованный ранее матричный вариант разбивки строки посимвольно на группы индексов - вначале строка с подстрокой поиска превращаются в набор байтов - берется первый и последний байт подстроки - по ним осуществляется индексация всех вхождений подобных символов/кодов/байтов в строке - пару вложенных циклов с замером расстояния между символами/кодами - встречное сравнение всех промежуточных байтов - инкреация счетчика и запись начальных индексов в выходной массив - индексы вхождений
На 100кк символьной строке поиск подстроки из трех символов на моей древней АМДэшке занял чуть более 14 сек (около 900 вхождений). Скорость работы зависит от количества подстрок в основной строке. Сравнения с учетом регистра. В качестве UDFки на листе будет возвращать только количество вхождений подстроки.
Тестер:
Скрытый текст
Код
Sub test()
Dim txt$, dt$, tt#, a&, arrP
TextGen txt, 100000000, 100000000
TextGen dt, 3, 3
tt = Timer
a = TxtFindCount(txt, dt, arrP)
tt = Timer - tt
End Sub
'генератор символов
Function TextGen(tt$, ByVal ss&, ByVal ll&)
Dim aa As Byte, x&, t$, arr() As Byte, a As Byte, c&, bb() As Byte
ReDim arr(1 To 2, 1 To 2)
arr(1, 1) = 65: arr(1, 2) = 25: arr(2, 1) = 97: arr(2, 2) = 25
'arr(3, 1) = 192: arr(3, 2) = 31: arr(4, 1) = 224: arr(4, 2) = 31
t = ""
If ll - ss > 0 Then c = ss + (Rnd * (ll - ss)) Else c = ll
ReDim bb(1 To c)
For x = 1 To c
a = Rnd * 2
If a = 0 Then a = 1
aa = arr(a, 1) + (Rnd * arr(a, 2))
bb(x) = aa
Next
tt = StrConv(bb, 64)
End Function
Поисковик:
Скрытый текст
Код
Function TxtFindCount&(iStr$, ByVal txt$, Optional arrP)
Dim mtrx(), ll&, ch1 As Byte, ch2 As Byte, bb() As Byte, aa() As Byte, a&, b&, c&
Dim x&, arr&(), a1&, a2&, c1&, c2&, ff As Boolean
ll = Len(txt): aa = StrConv(txt, 128): bb = StrConv(iStr, 128)
ch1 = aa(0): ch2 = aa(UBound(aa)): x = 0: c = 2
ReDim mtrx(0 To 255)
For a = 0 To UBound(bb): mtrx(bb(a)) = mtrx(bb(a)) + 1: Next
For a = 0 To UBound(bb)
If ch1 = bb(a) Or ch2 = bb(a) Then
If Not IsArray(mtrx(bb(a))) Then
ReDim arr(1 To mtrx(bb(a)) + 1): arr(1) = 2: arr(2) = a: mtrx(bb(a)) = arr()
Else: mtrx(bb(a))(1) = mtrx(bb(a))(1) + 1: mtrx(bb(a))(mtrx(bb(a))(1)) = a
End If
End If
Next
If Not IsArray(mtrx(ch1)) Or Not IsArray(mtrx(ch2)) Then TxtFindCount = 0: Exit Function
If ll = 1 Then
TxtFindCount = mtrx(ch1)(1) - 1: ReDim arrP(1 To mtrx(ch1)(1) - 1)
For a = 2 To mtrx(ch1)(1): arrP(a - 1) = mtrx(ch1)(a) + 1: Next
Exit Function
End If
ReDim arrP(1 To 1)
For a = 2 To mtrx(ch1)(1)
For b = c To mtrx(ch2)(1)
If mtrx(ch2)(b) > mtrx(ch1)(a) Then
c = b
If mtrx(ch1)(a) + ll - 1 <> mtrx(ch2)(b) Then Exit For
a1 = mtrx(ch1)(a): a2 = mtrx(ch2)(b)
c1 = 0: c2 = UBound(aa): ff = True: c = b + 1
Do While a2 - a1 > 1
c1 = c1 + 1: c2 = c2 - 1: a1 = a1 + 1: a2 = a2 - 1
If aa(c1) = bb(a1) Then
If aa(c2) <> bb(a2) Then ff = False: Exit Do
ff = True
Else: ff = False: Exit Do
End If
Loop
If ff Then x = x + 1: ReDim Preserve arrP(1 To x): arrP(x) = mtrx(ch1)(a) + 1: Exit For
End If
Next
Next
If x = 0 Then Erase arrP
TxtFindCount = x
End Function
Anchoret, не слишком ли сложно? Вот функция, которая делает то же самое, с использованием удобной и быстрой функции Instr, и кстати возвращает правильные индексы в массиве (или я не понял, что возвращает в массиве ваша функция). На строке длиной 100 млн у меня ошибка Out of memory на команде
Код
bb = StrConv(iStr, 128)
, хотя свободной памяти 800МБ (WinXP SP3, Офис 2007 или 2010). Так что пробовал со строке длиной 50 млн.
Скрытый текст
Код
Sub test()
Dim txt$, dt$, tt!, a&, arrP, a2&, arrP2
TextGen txt, 50000000, 50000000
TextGen dt, 3, 3
DoEvents: tt = Timer
a = TxtFindCount(txt, dt, arrP)
Debug.Print Timer - tt, a
DoEvents: tt = Timer
a2 = TxtFindCount2(txt, dt, arrP2)
Debug.Print Timer - tt, a2
For a = 1 To 10
Debug.Print arrP(a); Tab; Mid$(txt, arrP(a), 3), arrP2(a); Tab; Mid$(txt, arrP2(a), 3)
Next
End Sub
Function TxtFindCount2&(iStr$, txt$, Optional arrP)
Dim i&, j&, tl&
tl = Len(txt)
i = 1 - tl
ReDim arrP(1 To 256)
On Error GoTo 1
Do
i = InStr(i + tl, iStr, txt)
j = j + 1: arrP(j) = i
Loop Until i = 0
ReDim Preserve arrP(1 To j - 1)
TxtFindCount2 = j - 1
Exit Function
1 If Err = 9 Then 'Subscript out of range
ReDim Preserve arrP(1 To UBound(arrP) * 2)
Resume
Else
Err.Raise Err
End If
End Function
Казанский написал: или я не понял, что возвращает в массиве ваша функция
Все верно, там ошибка. При разбитии строки на байты массив начинается с нуля. Поэтому и индексы там не правильные. Вот:
Скрытый текст
Код
100000000 14,755859375 858
100000000 0,169921875 858
50698 GOl 50698 GOl
115805 GOl 115805 GOl
149460 GOl 149460 GOl
203361 GOl 203361 GOl
546622 GOl 546622 GOl
560392 GOl 560392 GOl
571115 GOl 571115 GOl
600086 GOl 600086 GOl
640550 GOl 640550 GOl
670922 GOl 670922 GOl
Да, и раз InStr способна окучивать такие объемы, то моя недофункция не нужна Соревноваться в скорости со встроенными функциями и используя при этом одну из них - глупо.Если только не использовать InStrB. Возможно потом проверю.