From cb99c1d716f7d06f928a975622c3df8bd030449d Mon Sep 17 00:00:00 2001 From: Derek Gulbranson Date: Sat, 4 Jul 2026 03:27:09 -0700 Subject: [PATCH 1/2] Simplify join_on_conjunctions bookkeeping Dedup the conjunction-index shift loop into a shift_conj_index helper, replace the pop()/try-except-IndexError boundary check with an equivalent slice delete, and replace slice-and-rebuild list reconstruction (pieces = pieces[:i] + [x] + pieces[j:]) with in-place slice assignment. No behavior change; full test suite still passes. Co-Authored-By: Claude Sonnet 5 --- nameparser/parser.py | 33 ++++++++++++++------------------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/nameparser/parser.py b/nameparser/parser.py index cd75c49..6a4664a 100644 --- a/nameparser/parser.py +++ b/nameparser/parser.py @@ -1280,6 +1280,13 @@ def register_joined_piece(new_piece: str, neighbor: str) -> None: # chain onto a following prefix/lastname (see "von und zu") self.C.prefixes.add(new_piece) + def shift_conj_index(past: int, by: int) -> None: + # after removing pieces at/after `past`, indices of the + # remaining conjunctions need to shift down by `by` + for j, val in enumerate(conj_index): + if val > past: + conj_index[j] = val - by + for i in conj_index: if len(pieces[i]) == 1 and total_length < 4 and pieces[i].isalpha(): # if there are only 3 total parts (minus known titles, suffixes @@ -1293,27 +1300,15 @@ def register_joined_piece(new_piece: str, neighbor: str) -> None: register_joined_piece(new_piece, pieces[i+1]) pieces[i] = new_piece pieces.pop(i+1) - # subtract 1 from the index of all the remaining conjunctions - for j, val in enumerate(conj_index): - if val > i: - conj_index[j] = val-1 + shift_conj_index(past=i, by=1) else: new_piece = " ".join(pieces[i-1:i+2]) register_joined_piece(new_piece, pieces[i-1]) pieces[i-1] = new_piece - pieces.pop(i) - rm_count = 2 - try: - pieces.pop(i) - except IndexError: - rm_count = 1 - - # subtract the number of removed pieces from the index - # of all the remaining conjunctions - for j, val in enumerate(conj_index): - if val > i: - conj_index[j] = val - rm_count + rm_count = min(2, len(pieces) - i) + del pieces[i:i+rm_count] + shift_conj_index(past=i, by=rm_count) # join prefixes to following lastnames: ['de la Vega'], ['van Buren'] prefixes = list(filter(self.is_prefix, pieces)) @@ -1343,7 +1338,7 @@ def register_joined_piece(new_piece: str, neighbor: str) -> None: # if there are two prefixes in sequence, join to the following piece j += 1 new_piece = ' '.join(pieces[i:j]) - pieces = pieces[:i] + [new_piece] + pieces[j:] + pieces[i:j] = [new_piece] except StopIteration: try: # if there are no more prefixes, look for a suffix to stop at @@ -1355,12 +1350,12 @@ def register_joined_piece(new_piece: str, neighbor: str) -> None: # before the prefix) would be matched instead. j = pieces.index(stop_at, i + 1) new_piece = ' '.join(pieces[i:j]) - pieces = pieces[:i] + [new_piece] + pieces[j:] + pieces[i:j] = [new_piece] except StopIteration: # if there were no suffixes, nothing to stop at so join all # remaining pieces new_piece = ' '.join(pieces[i:]) - pieces = pieces[:i] + [new_piece] + pieces[i:] = [new_piece] log.debug("pieces: %s", pieces) return pieces From 133a01c56bb97db685de6afc3eeb7b66feed5d8b Mon Sep 17 00:00:00 2001 From: Derek Gulbranson Date: Sat, 4 Jul 2026 03:30:30 -0700 Subject: [PATCH 2/2] Guard against silent no-op deletion in join_on_conjunctions PR review flagged that the del-slice replacement for the old pop()/try-except boundary check silently no-ops on an out-of-range index instead of raising, unlike the original. That state is currently unreachable, but add an assertion so a future regression in the index bookkeeping fails loudly instead of silently corrupting pieces. Co-Authored-By: Claude Sonnet 5 --- nameparser/parser.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/nameparser/parser.py b/nameparser/parser.py index 6a4664a..d4d9cd6 100644 --- a/nameparser/parser.py +++ b/nameparser/parser.py @@ -1306,7 +1306,10 @@ def shift_conj_index(past: int, by: int) -> None: new_piece = " ".join(pieces[i-1:i+2]) register_joined_piece(new_piece, pieces[i-1]) pieces[i-1] = new_piece + # len(pieces) - i is always >= 1 here: pieces[i-1:i+2] above + # already accessed index i, so i is guaranteed in range. rm_count = min(2, len(pieces) - i) + assert rm_count > 0, f"unexpected empty deletion at i={i}, pieces={pieces}" del pieces[i:i+rm_count] shift_conj_index(past=i, by=rm_count)