I would like to thank everone who has been patient enough through the
development of this algorithm, I was unable to get any decent reference
material on Bob Boyer's original design, the original is paper only
archive so I have had to work out the logic from scratch based on the
scant amount of detailed technical data around and keep testing it until
it worked on all cases.
The basics of the algorithm are that it scans the pattern being searched
for backwards until it finds a mismatch, if the character is within the
characters that are in the pattern, it calculates what is called a "Good
suffix shift" but if the character that causes the mismatch is not within
the pattern, it performs what is called a "bad character shift".
The secret with a Boyer Moore search ago is the capacity make shifts of more
than one character, in normal mixed characters as in binary or text there is
a reasonable chance of getting a shift of the full length of the pattern
which is a lot faster than scanning every character in the pattern.
The Good Suffix Shift is derived from a table that is constructed before the
search begins that fills every position with the length of the pattern, then
overwrites the specific character position with the shift for its position in
the pattern.
By comparing the mismatch with the character in the table, it is easy to
determine if the character is within the pattern or not, if its not, a "bad
character shift" is performed, if it is, a "Good Suffix Shift" is calculated.
Where I got into trouble was in calculating the Good Suffix Shift, there are
some situations where you have repeat characters prefixed by a different
character, EG Xyyyyyy. Because the table is overwritten at the same location
with repeat characters, the "x" has the shift value of 1 where the "Y" has a
value of 6 and this made the shift I was using go past the correct position
with search patterns of this type in some instances.
The solution was to calculate the number of matches that had been made in the
compare loop and subtract that from the shift, check that it was not zero or
less, set the shift at 1 if it was and then add the shift to the register
that was the current position counter.
The code design is based on testing of a number of alternative layouts, after
the different calculation was added and with some instruction reordering, the
additional 5 instructions only slowed it down by about 1 or 2 percent on the
PIII 600 I write on and other variations did not effect its speed on the PIII
much at all but the AMD that I also test with showed a reasonable range of
speed difference so I optimised it to the AMD as there was no real difference
on the PIII.
The test code below is what I have used to test the algorithm, it shows after
hundreds of thousands of searches that there are no misses so I think the
algorithm is now safe for general purpose searching. The code,
can be replaced with a single instruction,
which produces one of the simplified variations of the Boyer Moore algo but
it is measurably slower that the complete version.
A little pure PowerBASIC for your pleasure.
Regards,
[email protected]
test code
~~~~~~~~~
Working Function
~~~~~~~~~~~~~~~~
------------------
development of this algorithm, I was unable to get any decent reference
material on Bob Boyer's original design, the original is paper only
archive so I have had to work out the logic from scratch based on the
scant amount of detailed technical data around and keep testing it until
it worked on all cases.
The basics of the algorithm are that it scans the pattern being searched
for backwards until it finds a mismatch, if the character is within the
characters that are in the pattern, it calculates what is called a "Good
suffix shift" but if the character that causes the mismatch is not within
the pattern, it performs what is called a "bad character shift".
Code:
| source => xxxxxYxxxxxYxxxxxZ pattern => xxxxxZ | The character Y is not in the search pattern and you cannot get a match by scanning back any further so the algo shifts the start position to the next position past that character and starts the reverse compare again. | source => xxxxxYxxxxxYxxxxxZ pattern => xxxxxZ |
than one character, in normal mixed characters as in binary or text there is
a reasonable chance of getting a shift of the full length of the pattern
which is a lot faster than scanning every character in the pattern.
The Good Suffix Shift is derived from a table that is constructed before the
search begins that fills every position with the length of the pattern, then
overwrites the specific character position with the shift for its position in
the pattern.
Code:
pattern => algorithm 87654321 good suffix shift 123456789 bad char shift
determine if the character is within the pattern or not, if its not, a "bad
character shift" is performed, if it is, a "Good Suffix Shift" is calculated.
Where I got into trouble was in calculating the Good Suffix Shift, there are
some situations where you have repeat characters prefixed by a different
character, EG Xyyyyyy. Because the table is overwritten at the same location
with repeat characters, the "x" has the shift value of 1 where the "Y" has a
value of 6 and this made the shift I was using go past the correct position
with search patterns of this type in some instances.
The solution was to calculate the number of matches that had been made in the
compare loop and subtract that from the shift, check that it was not zero or
less, set the shift at 1 if it was and then add the shift to the register
that was the current position counter.
The code design is based on testing of a number of alternative layouts, after
the different calculation was added and with some instruction reordering, the
additional 5 instructions only slowed it down by about 1 or 2 percent on the
PIII 600 I write on and other variations did not effect its speed on the PIII
much at all but the AMD that I also test with showed a reasonable range of
speed difference so I optimised it to the AMD as there was no real difference
on the PIII.
The test code below is what I have used to test the algorithm, it shows after
hundreds of thousands of searches that there are no misses so I think the
algorithm is now safe for general purpose searching. The code,
Code:
Set_Suffix_Shift: ! add eax, ecx ; add CMP count ! sub eax, cval ; sub loop count ! cmp eax, 0 ; test eax for zero ! jg Add_Suffix_Shift ! mov eax, 1 ; minimum shift is 1 Add_Suffix_Shift: ! add esi, eax ; add suffix shift
Code:
! inc esi
it is measurably slower that the complete version.
A little pure PowerBASIC for your pleasure.

Regards,
[email protected]
test code
~~~~~~~~~
Code:
Open "win32api.inc" for Binary as #1 Get$ #1,lof(1),a$ Close #1 rf& = 0 spos& = 0 ln& = 8 misses& = 0 Do b$ = mid$(a$,spos&,ln&) ! inc spos& ! inc ln& If ln& > 240 Then ln& = 8 rv& = BMBinSearch(0,StrPtr(a$),len(a$),StrPtr(b$),len(b$)) If rv& = -1 Then ! inc misses& End If SetWindowText hWnd,ByCopy str$(rv&)+" "+str$(rf&)+" "+str$(misses&) ! inc rf& Loop while rf& < 700000
~~~~~~~~~~~~~~~~
Code:
' ######################################################################### FUNCTION BMBinSearch(ByVal startpos as LONG, _ ByVal lpSource as LONG,ByVal srcLngth as LONG, _ ByVal lpSubStr as LONG,ByVal subLngth as LONG) as LONG #REGISTER NONE LOCAL eLen as DWORD LOCAL cval as DWORD LOCAL shift_table as STRING * 1024 ! cmp subLngth, 1 ! jg OKsize ! mov eax, -2 ; string too short, must be > 1 ! jmp Cleanup OKsize: ! mov esi, lpSource ! add esi, srcLngth ! sub esi, subLngth ! mov eLen, esi ; set Exit Length ' ---------------------------------------- ' load shift table with value in subLngth ' ---------------------------------------- ! mov ecx, 256 ! mov eax, subLngth ! lea edi, shift_table ! rep stosd ' ---------------------------------------------- ' load decending count values into shift table ' ---------------------------------------------- ! mov ecx, subLngth ; SubString length in ECX ! dec ecx ; correct for zero based index ! mov esi, lpSubStr ; address of SubString in ESI ! lea edi, shift_table ! xor eax, eax Write_Shift_Chars: ! mov al, [esi] ; get the character ! inc esi ! mov [edi+eax*4], ecx ; write shift for each character ! dec ecx ; to ascii location in table ! jnz Write_Shift_Chars ' ----------------------------- ' set up for main compare loop ' ----------------------------- ! mov ecx, subLngth ! dec ecx ! mov cval, ecx ! mov esi, lpSource ! mov edi, lpSubStr ! add esi, startpos ; add starting position ! mov edx, subLngth ; pattern length in edx ! jmp Cmp_Loop ' *********************** Loop Code *************************** Set_Suffix_Shift: ! add eax, ecx ; add CMP count ! sub eax, cval ; sub loop count ! cmp eax, 0 ; test eax for zero ! jg Add_Suffix_Shift ! mov eax, 1 ; minimum shift is 1 Add_Suffix_Shift: ! add esi, eax ; add suffix shift Pre_Cmp: ! cmp esi, eLen ; test exit length ! ja No_Match ! xor eax, eax ; reset EAX ! mov ecx, cval ; reset counter in compare loop Cmp_Loop: ! mov al, [esi+ecx] ! cmp al, [edi+ecx] ; cmp characters in ESI / EDI ! jne Get_Shift ; if not equal, get next shift ! dec ecx ! jns Cmp_Loop ! jmp Match Get_Shift: ! mov eax, shift_table[eax*4] ; get char shift value ! cmp eax, edx ; is eax pattern length ? ! jne Set_Suffix_Shift ; if not, jump to Calc_Suffix_Shift ! lea esi, [esi+ecx+1] ; add bad char shift ! jmp Pre_Cmp ' *************************************************************** Match: ! sub esi, lpSource ; sub source from ESI ! mov eax, esi ; put length in eax ! jmp Cleanup No_Match: ! mov eax, -1 ; set value for no match Cleanup: ! mov FUNCTION, eax END FUNCTION ' ########################################################################
------------------
Comment