## # 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