From 8222199a7d427b4e66e71c0570e6d382d40eec2f Mon Sep 17 00:00:00 2001 From: ddidderr Date: Sun, 26 Apr 2026 13:48:19 +0200 Subject: [PATCH] feat: accept search limit as CLI argument Allow users to override the default HCN search limit by passing one optional positive integer argument. Bad input now exits with status 2 and prints a clear message instead of silently using a fixed range. Keep the existing one-billion default when no argument is provided, so current usage remains unchanged. Test Plan: - cargo clippy - cargo clippy --benches - cargo clippy --tests - cargo test Refs: N/A --- src/main.rs | 60 +++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 56 insertions(+), 4 deletions(-) diff --git a/src/main.rs b/src/main.rs index 0aae7b9..301a914 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,6 @@ -use std::time::Instant; +use std::{env, process, time::Instant}; -const SEARCH_LIMIT: u64 = 1_000_000_000; +const DEFAULT_SEARCH_LIMIT: u64 = 1_000_000_000; const FIRST_PRIMES: &[u64] = &[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31]; #[derive(Clone, Copy, Debug, Eq, PartialEq)] @@ -108,9 +108,36 @@ fn hcn_records(limit: u64) -> Vec { records } +fn parse_search_limit(args: &[String]) -> Result { + let program = args.first().map_or("hcn", String::as_str); + + match args.len() { + 0 | 1 => Ok(DEFAULT_SEARCH_LIMIT), + 2 => { + let raw_limit = &args[1]; + let limit = raw_limit + .parse::() + .map_err(|err| format!("invalid search limit {raw_limit:?}: {err}"))?; + + if limit == 0 { + return Err("search limit must be greater than zero".to_owned()); + } + + Ok(limit) + } + _ => Err(format!("usage: {program} [SEARCH_LIMIT]")), + } +} + fn main() { + let args = env::args().collect::>(); + let search_limit = parse_search_limit(&args).unwrap_or_else(|err| { + eprintln!("{err}"); + process::exit(2); + }); + let start = Instant::now(); - let records = hcn_records(SEARCH_LIMIT); + let records = hcn_records(search_limit); for Candidate { nr, divisors } in records { println!("{nr}: {divisors} ({:?} since start)", start.elapsed()); @@ -119,7 +146,7 @@ fn main() { #[cfg(test)] mod tests { - use super::{Candidate, hcn_records, max_exponent}; + use super::{Candidate, DEFAULT_SEARCH_LIMIT, hcn_records, max_exponent, parse_search_limit}; fn count_divisors(mut nr: u64) -> u64 { let mut divisor_count = 1; @@ -233,4 +260,29 @@ mod tests { fn matches_brute_force_records_up_to_ten_thousand() { assert_eq!(hcn_records(10_000), brute_force_hcn_records(10_000)); } + + #[test] + fn parses_default_search_limit_without_arg() { + assert_eq!( + parse_search_limit(&["hcn".to_owned()]), + Ok(DEFAULT_SEARCH_LIMIT) + ); + } + + #[test] + fn parses_search_limit_arg() { + assert_eq!( + parse_search_limit(&["hcn".to_owned(), "42000".to_owned()]), + Ok(42_000) + ); + } + + #[test] + fn rejects_invalid_search_limit_arg() { + assert!(parse_search_limit(&["hcn".to_owned(), "nope".to_owned()]).is_err()); + assert!(parse_search_limit(&["hcn".to_owned(), "0".to_owned()]).is_err()); + assert!( + parse_search_limit(&["hcn".to_owned(), "100".to_owned(), "200".to_owned()]).is_err() + ); + } }