Skip to content

fix: prevent byte/char index mismatch panic in jaro_winkler_distance#1056

Open
xuanwujian wants to merge 1 commit into
TheAlgorithms:masterfrom
xuanwujian:issue-1047-fix-jaro-winkler-non-ascii-panic
Open

fix: prevent byte/char index mismatch panic in jaro_winkler_distance#1056
xuanwujian wants to merge 1 commit into
TheAlgorithms:masterfrom
xuanwujian:issue-1047-fix-jaro-winkler-non-ascii-panic

Conversation

@xuanwujian

Copy link
Copy Markdown

Fixes #1047

Description

jaro_winkler_distance mixed character indices (from .chars().enumerate()) with byte indices (str::len() and &str slicing) inside get_matched_characters. When a computed slice boundary fell inside a multi-byte UTF-8 character, &str slicing panics (byte index N is not a char boundary). Any non-ASCII input — e.g. jaro_winkler_distance("ab", "céd") — crashed the caller's process.

Beyond the panic, the score formula's length denominators (str1.len() / str2.len()) and the prefix-matching bound were also byte lengths, so a fix that only silenced the panic would still compute a mathematically wrong Jaro score for any multi-byte input (byte length over-counts relative to character count).

Fix: rewrote the function to operate on Vec<char> consistently instead of byte-indexed &str:

  • str1/str2 are converted to Vec<char> once at the top; get_matched_characters now takes &[char] and does all windowing/searching by character position.
  • The matched-character "removal" still uses the original placeholder approach (replacing the character rather than deleting it), just on a Vec<char> instead of a String — this preserves the exact existing algorithmic behavior/scores for ASCII input (all 6 pre-existing ASCII test cases produce identical results).
  • The Jaro score denominators and the prefix-match bound/loop now use character counts, so results are correct — not just panic-free — for multi-byte input.
  • Public signature is unchanged: pub fn jaro_winkler_distance(str1: &str, str2: &str) -> f64.
  • Left the two secondary/non-blocking observations from the issue thread (matching-window formula vs. the textbook Jaro definition, and first-occurrence-vs-window find semantics for repeated characters) untouched, as noted in the issue as "not the main bug."

Added 3 regression tests: the exact issue repro ("ab"/"céd", no panic), two fully non-ASCII identical strings (CJK 测试/测试 and accented áé/áé), and a partial multi-byte match (测试/测a) with a hand-verified expected score.

Type of change

  • Bug fix (non-breaking change which fixes an issue)

Checklist:

  • I ran bellow commands using the latest version of rust nightly.
  • I ran cargo clippy --all -- -D warnings just before my last commit and fixed any issue that was found. — cargo clippy (nightly) is clean for the file I changed (src/string/jaro_winkler_distance.rs); running it repo-wide with --all -- -D warnings currently fails due to pre-existing warnings in unrelated files on master, unaffected by this change.
  • I ran cargo fmt just before my last commit.
  • I ran cargo test just before my last commit and all tests passed.
  • I added my algorithm to the corresponding mod.rs file within its own folder, and in any parent folder(s). (n/a — existing algorithm, already registered)
  • I added my algorithm to DIRECTORY.md with the correct link. (n/a — existing algorithm, already listed)
  • I checked CONTRIBUTING.md and my code follows its guidelines.

jaro_winkler_distance mixed character indices (from .chars().enumerate())
with byte indices (str::len() and &str slicing) when locating matched
characters. Slicing a &str at a byte offset that falls inside a
multi-byte UTF-8 character panics, so any non-ASCII input (e.g.
jaro_winkler_distance("ab", "céd")) crashed the caller's process.

Rewrite the function to operate on Vec<char> throughout instead of
byte-indexed &str, so both the matching-window logic and the score
formula's length denominators use character counts consistently. This
fixes the panic and also corrects the Jaro score itself for non-ASCII
input (byte-length denominators would otherwise under-weight
multi-byte strings even without panicking). The placeholder-based
match removal (replacing a matched character rather than deleting it)
is preserved so existing ASCII results are unchanged.

Added regression tests for the exact repro from the issue plus
CJK/accented-character cases.

Fixes TheAlgorithms#1047
@xuanwujian xuanwujian requested a review from imp2002 as a code owner July 12, 2026 15:35
@codecov-commenter

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 95.89%. Comparing base (c65d014) to head (4cb644c).

Additional details and impacted files
@@           Coverage Diff           @@
##           master    #1056   +/-   ##
=======================================
  Coverage   95.89%   95.89%           
=======================================
  Files         396      396           
  Lines       30442    30458   +16     
=======================================
+ Hits        29191    29208   +17     
+ Misses       1251     1250    -1     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

jaro_winkler_distance panics on non-ASCII input (byte/char index mismatch in string slicing)

3 participants