Merge pull request #198 from aparibocci/feature/boyer_moore_horspool_search

Implementing `Boyer-Moore-Horspool` substring search algorithm
This commit is contained in:
Stepfen Shawn 2023-04-11 15:07:31 +08:00 committed by GitHub
commit 42f63958eb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 80 additions and 0 deletions

View file

@ -0,0 +1,60 @@
##
# This class represents a table of {bad_match_character => slide_offset}
# to be used in Boyer-Moore-Horspool substring finding algorithm.
class BadMatchTable
attr_reader :pattern
attr_reader :table
def initialize(pattern)
@pattern = pattern
@table = {}
for i in 0...pattern.size
@table[pattern[i]] = pattern.size - 1 - i
end
end
##
# Given a mismatch character belonging to the search string, returns
# the offset to be used when sliding the pattern towards the right.
def slide_offset(mismatch_char)
table.fetch(mismatch_char, pattern.size)
end
end
##
# Returns the first starting index of the given pattern's occurrence (as a substring)
# in the provided search string if a match is found, -1 otherwise.
def first_match_index(search_string, pattern)
matches = matches_indices(search_string, pattern, true)
matches.empty? ? -1 : matches[0]
end
##
# Returns the list of starting indices of the given pattern's occurrences (as a substring)
# in the provided search string.
# If no match is found, an empty list is returned.
# If `stop_at_first_match` is provided as `true`, the returned list will contain at most one element,
# being the leftmost encountered match in the search string.
def matches_indices(search_string, pattern, stop_at_first_match=false)
table = BadMatchTable.new(pattern)
i = pattern.size - 1
indices = []
while i < search_string.size
for j in 0...pattern.size
if search_string[i-j] != pattern[pattern.size-1-j]
i += table.slide_offset(search_string[i-j])
break
elsif j == pattern.size-1
indices.append(i-j)
return indices if stop_at_first_match
i += 1
end
end
end
indices
end

View file

@ -0,0 +1,20 @@
require 'minitest/autorun'
require_relative 'boyer_moore_horspool_search'
class TestBoyerMooreHorspoolSearch < Minitest::Test
def test_first_match_returns_negative_index_if_no_match
assert first_match_index('abcdefghijk', 'defz') < 0
end
def test_first_match_returns_first_match_index
assert first_match_index('abcdefghijkghilmno', 'ghi') == 6
end
def test_match_indices_returns_empty_list_if_no_match
assert matches_indices('abcdefghijk', 'defz').empty?
end
def test_match_indices_returns_list_of_match_indices
assert matches_indices('abcdefghijkghilmno', 'ghi') == [6, 11]
end
end