Skip to content

Commit c38f004

Browse files
authored
perf(lambda-rs-args): Prune candidates shorter than the best levenshtein distance (#193)
## Summary Improve unknown-argument suggestion performance in `lambda-rs-args` by pruning candidates that cannot beat the current best Levenshtein distance based on character-length lower bounds. ## Related Issues N/A ## Changes - Added an early-prune in `unknown_with_suggestion` to skip Levenshtein calls when `abs(len(key) - len(arg)) >= best_distance`. - Kept suggestion output behavior unchanged (`did you mean ...`). - Added stress coverage with many unrelated flags to validate suggestion quality under larger candidate sets. ## Type of Change - [ ] Bug fix (non-breaking change that fixes an issue) - [ ] Feature (non-breaking change that adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Documentation (updates to docs, specs, tutorials, or comments) - [x] Refactor (code change that neither fixes a bug nor adds a feature) - [x] Performance (change that improves performance) - [x] Test (adding or updating tests) - [ ] Build/CI (changes to build process or CI configuration) ## Affected Crates - [ ] `lambda-rs` - [ ] `lambda-rs-platform` - [x] `lambda-rs-args` - [ ] `lambda-rs-logging` - [ ] Other: ## Checklist - [ ] Code follows the repository style guidelines (`cargo +nightly fmt --all`) - [ ] Code passes clippy (`cargo clippy --workspace --all-targets -- -D warnings`) - [ ] Tests pass (`cargo test --workspace`) - [x] New code includes appropriate documentation - [ ] Public API changes are documented - [ ] Breaking changes are noted in this PR description ## Testing **Commands run:** ```bash cargo test -p lambda-rs-args ``` **Manual verification steps (if applicable):** 1. Trigger parse with an unknown flag similar to a known one (e.g. `--portt` vs `--port`) and verify suggestion output remains correct. ## Screenshots/Recordings N/A ## Platform Testing - [x] macOS - [ ] Windows - [ ] Linux ## Additional Notes Asymptotics: worst-case remains `O(A * L^2)` for `A` candidate args and `L` string length, but the prune reduces expensive Levenshtein evaluations from `O(A)` to `O(K)` in common cases (`K <= A`) where many candidates are length-mismatched.
2 parents f0895f1 + 245dfa1 commit c38f004

1 file changed

Lines changed: 31 additions & 0 deletions

File tree

  • crates/lambda-rs-args/src

crates/lambda-rs-args/src/lib.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1307,6 +1307,28 @@ mod tests {
13071307
}
13081308
}
13091309

1310+
#[test]
1311+
fn unknown_argument_suggests_with_many_unrelated_keys() {
1312+
let mut parser = ArgumentParser::new("app")
1313+
.with_argument(Argument::new("--port").with_type(ArgumentType::Integer));
1314+
for i in 0..500 {
1315+
let long_name = Box::leak(
1316+
format!("--very-long-unrelated-argument-name-{}", i).into_boxed_str(),
1317+
);
1318+
parser = parser.with_argument(
1319+
Argument::new(long_name).with_type(ArgumentType::String),
1320+
);
1321+
}
1322+
1323+
let err = parser.parse(&argv(&["--portt", "1"])).unwrap_err();
1324+
match err {
1325+
ArgsError::UnknownArgument(msg) => {
1326+
assert!(msg.contains("did you mean '--port'"))
1327+
}
1328+
_ => panic!(),
1329+
}
1330+
}
1331+
13101332
#[test]
13111333
fn missing_value_error() {
13121334
let parser = ArgumentParser::new("app")
@@ -1512,7 +1534,16 @@ fn read_config_file(
15121534

15131535
fn unknown_with_suggestion(arg: &str, parser: &ArgumentParser) -> String {
15141536
let mut best: Option<(usize, String)> = None;
1537+
let arg_len = arg.chars().count();
15151538
for key in parser.args.keys() {
1539+
// Levenshtein distance has a hard lower bound of the character-length gap.
1540+
// If that bound cannot beat the current best score, skip this candidate.
1541+
if let Some((best_distance, _)) = best.as_ref() {
1542+
let key_len = key.chars().count();
1543+
if key_len.abs_diff(arg_len) >= *best_distance {
1544+
continue;
1545+
}
1546+
}
15161547
let d = levenshtein(arg, key);
15171548
if best.as_ref().map(|(bd, _)| d < *bd).unwrap_or(true) {
15181549
best = Some((d, key.clone()));

0 commit comments

Comments
 (0)