@@ -78,23 +78,67 @@ def mismatch_in_text(self, current_pos: int) -> int:
7878
7979 def bad_character_heuristic (self ) -> list [int ]:
8080 """
81- Finds the positions of the pattern location.
81+ Finds the positions of the pattern occurrence.
82+
83+ The previous implementation assigned the shift to the for-loop variable
84+ ``i`` inside the loop; in Python that reassignment has no effect on the
85+ iteration, so the bad-character shift was silently ignored and the search
86+ degenerated to a plain O(n*m) scan.
87+
88+ This version uses a ``while`` loop so the shift actually takes effect.
89+ On a mismatch at text position ``mismatch_index``, it aligns the pattern
90+ with the right-most occurrence of the mismatched character that lies
91+ strictly to the left of the mismatch offset. If no such occurrence exists,
92+ it moves the pattern entirely past the mismatch.
93+
94+ Correctness (why the shift never skips a valid occurrence):
95+
96+ At any alignment ``i`` we first scan the pattern from right to left and
97+ find the right-most mismatch at pattern offset ``mismatch_offset`` (so
98+ everything to its right already agrees). The mismatching text character
99+ is ``char``. The loop then advances ``i`` by ``shift``:
100+
101+ * If ``char`` occurs at some index ``r < mismatch_offset`` (right-most such
102+ ``r``), set ``shift = mismatch_offset - r``. Any skipped alignment
103+ ``i < i' < i + shift`` maps the mismatching text position onto a pattern
104+ index strictly between ``r`` and ``mismatch_offset``, where every
105+ character is ``!= char``, so ``i'`` cannot be a match.
106+ * Otherwise ``char`` does not occur at all to the left of the mismatch,
107+ so all skipped alignments ``i < i' < i + mismatch_offset + 1`` put a
108+ character ``!= char`` at the mismatching text position, and cannot be
109+ matches either.
110+
111+ Because every jump maps the mismatching text position onto a pattern
112+ character unequal to it, no occurrence can be skipped. This property is
113+ machine-verified (soundness: every emitted position is a real match, and
114+ completeness: every real match is emitted) with the Dafny verifier.
82115
83116 >>> bms = BoyerMooreSearch(text="ABAABA", pattern="AB")
84117 >>> bms.bad_character_heuristic()
85118 [0, 3]
86119 """
87120
88121 positions = []
89- for i in range (self .textLen - self .patLen + 1 ):
122+ i = 0
123+ while i <= self .textLen - self .patLen :
90124 mismatch_index = self .mismatch_in_text (i )
91125 if mismatch_index == - 1 :
92126 positions .append (i )
127+ i += 1
93128 else :
94- match_index = self .match_in_pattern (self .text [mismatch_index ])
95- i = (
96- mismatch_index - match_index
97- ) # shifting index lgtm [py/multiple-definition]
129+ mismatch_offset = mismatch_index - i
130+ char = self .text [mismatch_index ]
131+ shift = 1
132+ for j in range (mismatch_offset - 1 , - 1 , - 1 ):
133+ if self .pattern [j ] == char :
134+ shift = mismatch_offset - j
135+ break
136+ else :
137+ # char not present to the left of the mismatch offset:
138+ # shift the pattern entirely past the mismatch
139+ shift = mismatch_offset + 1
140+ i += shift
141+
98142 return positions
99143
100144
0 commit comments