Skip to content

Commit 42f6395

Browse files
authored
Merge pull request #198 from aparibocci/feature/boyer_moore_horspool_search
Implementing `Boyer-Moore-Horspool` substring search algorithm
2 parents cae9ac5 + bea6a1f commit 42f6395

2 files changed

Lines changed: 80 additions & 0 deletions

File tree

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
##
2+
# This class represents a table of {bad_match_character => slide_offset}
3+
# to be used in Boyer-Moore-Horspool substring finding algorithm.
4+
5+
class BadMatchTable
6+
7+
attr_reader :pattern
8+
attr_reader :table
9+
10+
def initialize(pattern)
11+
@pattern = pattern
12+
@table = {}
13+
for i in 0...pattern.size
14+
@table[pattern[i]] = pattern.size - 1 - i
15+
end
16+
end
17+
18+
##
19+
# Given a mismatch character belonging to the search string, returns
20+
# the offset to be used when sliding the pattern towards the right.
21+
22+
def slide_offset(mismatch_char)
23+
table.fetch(mismatch_char, pattern.size)
24+
end
25+
end
26+
27+
##
28+
# Returns the first starting index of the given pattern's occurrence (as a substring)
29+
# in the provided search string if a match is found, -1 otherwise.
30+
31+
def first_match_index(search_string, pattern)
32+
matches = matches_indices(search_string, pattern, true)
33+
matches.empty? ? -1 : matches[0]
34+
end
35+
36+
##
37+
# Returns the list of starting indices of the given pattern's occurrences (as a substring)
38+
# in the provided search string.
39+
# If no match is found, an empty list is returned.
40+
# If `stop_at_first_match` is provided as `true`, the returned list will contain at most one element,
41+
# being the leftmost encountered match in the search string.
42+
43+
def matches_indices(search_string, pattern, stop_at_first_match=false)
44+
table = BadMatchTable.new(pattern)
45+
i = pattern.size - 1
46+
indices = []
47+
while i < search_string.size
48+
for j in 0...pattern.size
49+
if search_string[i-j] != pattern[pattern.size-1-j]
50+
i += table.slide_offset(search_string[i-j])
51+
break
52+
elsif j == pattern.size-1
53+
indices.append(i-j)
54+
return indices if stop_at_first_match
55+
i += 1
56+
end
57+
end
58+
end
59+
indices
60+
end
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
require 'minitest/autorun'
2+
require_relative 'boyer_moore_horspool_search'
3+
4+
class TestBoyerMooreHorspoolSearch < Minitest::Test
5+
def test_first_match_returns_negative_index_if_no_match
6+
assert first_match_index('abcdefghijk', 'defz') < 0
7+
end
8+
9+
def test_first_match_returns_first_match_index
10+
assert first_match_index('abcdefghijkghilmno', 'ghi') == 6
11+
end
12+
13+
def test_match_indices_returns_empty_list_if_no_match
14+
assert matches_indices('abcdefghijk', 'defz').empty?
15+
end
16+
17+
def test_match_indices_returns_list_of_match_indices
18+
assert matches_indices('abcdefghijkghilmno', 'ghi') == [6, 11]
19+
end
20+
end

0 commit comments

Comments
 (0)