Skip to main content

algorithms_lib/
lib.rs

1//! Algorithms Library for Rust
2//!
3//! A comprehensive, zero-dependency collection of common algorithms across
4//! sorting, searching, graphs, dynamic programming, strings, greedy, and
5//! divide-and-conquer.
6//!
7//! # Conventions
8//!
9//! - Fallible operations return [`Result<T, Error>`](Error); lookups that miss
10//!   return `Err(Error::NotFound)` rather than a sentinel index.
11//! - `*_search` functions on strings report **byte offsets**, not char indices.
12//! - Most sorts take `&[T]` and return `Vec<T>`; `*_in_place` variants sort
13//!   `&mut [T]` without returning an allocation.
14//!
15//! # Example
16//!
17//! ```
18//! use algorithms_lib::{quick_sort, binary_search};
19//!
20//! let sorted = quick_sort(&[3, 1, 4, 1, 5]);
21//! assert_eq!(sorted, vec![1, 1, 3, 4, 5]);
22//! assert_eq!(binary_search(&sorted, &4), Ok(3));
23//! ```
24
25#![warn(missing_docs)]
26#![allow(
27    clippy::for_kv_map,
28    clippy::multiple_bound_locations,
29    clippy::needless_range_loop,
30    clippy::type_complexity,
31    clippy::unnecessary_lazy_evaluations,
32    clippy::useless_vec
33)]
34
35use std::cmp::Ordering;
36use std::cmp::Reverse;
37use std::collections::{BinaryHeap, HashMap, HashSet, VecDeque};
38use std::error::Error as StdError;
39use std::fmt;
40use std::hash::Hash;
41
42/// Errors that can be returned by algorithms in this crate.
43#[derive(Debug, Clone, PartialEq)]
44pub enum Error {
45    /// The requested value was not present (search miss, unreachable goal).
46    NotFound,
47    /// An argument violates the function's input contract.
48    InvalidInput,
49    /// The graph is malformed or violates the algorithm's weight requirements.
50    InvalidGraph,
51    /// A negative-weight cycle reachable from the start was detected.
52    NegativeCycle,
53}
54
55impl fmt::Display for Error {
56    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57        match self {
58            Error::NotFound => write!(f, "not found"),
59            Error::InvalidInput => write!(f, "invalid input"),
60            Error::InvalidGraph => write!(f, "invalid graph"),
61            Error::NegativeCycle => write!(f, "negative-weight cycle detected"),
62        }
63    }
64}
65
66impl StdError for Error {}
67
68// ---------------- Sorting ----------------
69
70/// Quick sort. Returns a new sorted `Vec`; see [`quick_sort_in_place`] to sort
71/// a mutable slice without allocating the result.
72pub fn quick_sort<T: Ord + Clone>(items: &[T]) -> Vec<T> {
73    if items.len() <= 1 {
74        return items.to_vec();
75    }
76    let pivot = items[items.len() / 2].clone();
77    let mut left = Vec::new();
78    let mut middle = Vec::new();
79    let mut right = Vec::new();
80    for x in items {
81        if *x < pivot {
82            left.push(x.clone());
83        } else if *x > pivot {
84            right.push(x.clone());
85        } else {
86            middle.push(x.clone());
87        }
88    }
89    let mut result = quick_sort(&left);
90    result.extend(middle);
91    result.extend(quick_sort(&right));
92    result
93}
94
95/// Stable merge sort.
96pub fn merge_sort<T: Ord + Clone>(items: &[T]) -> Vec<T> {
97    if items.len() <= 1 {
98        return items.to_vec();
99    }
100    let mid = items.len() / 2;
101    let left = merge_sort(&items[..mid]);
102    let right = merge_sort(&items[mid..]);
103    merge(&left, &right)
104}
105
106fn merge<T: Ord>(left: &[T], right: &[T]) -> Vec<T>
107where
108    T: Clone,
109{
110    let mut merged = Vec::with_capacity(left.len() + right.len());
111    let mut i = 0;
112    let mut j = 0;
113    while i < left.len() && j < right.len() {
114        if left[i] <= right[j] {
115            merged.push(left[i].clone());
116            i += 1;
117        } else {
118            merged.push(right[j].clone());
119            j += 1;
120        }
121    }
122    merged.extend_from_slice(&left[i..]);
123    merged.extend_from_slice(&right[j..]);
124    merged
125}
126
127/// Binary max-heap sort.
128pub fn heap_sort<T: Ord + Clone>(items: &[T]) -> Vec<T> {
129    let mut arr = items.to_vec();
130    let n = arr.len();
131    if n <= 1 {
132        return arr;
133    }
134    for i in (0..n / 2).rev() {
135        sift_down(&mut arr, n, i);
136    }
137    for end in (1..n).rev() {
138        arr.swap(0, end);
139        sift_down(&mut arr, end, 0);
140    }
141    arr
142}
143
144fn sift_down<T: Ord>(arr: &mut [T], n: usize, i: usize) {
145    let mut largest = i;
146    let left = 2 * i + 1;
147    let right = 2 * i + 2;
148    if left < n && arr[left] > arr[largest] {
149        largest = left;
150    }
151    if right < n && arr[right] > arr[largest] {
152        largest = right;
153    }
154    if largest != i {
155        arr.swap(i, largest);
156        sift_down(arr, n, largest);
157    }
158}
159
160/// Sorts `items` in place using quick sort's partitioning result.
161///
162/// This variant avoids returning an allocated `Vec`; internally it sorts a
163/// scratch buffer and copies back, so it does not require `T: Ord` callers to
164/// receive a new collection.
165pub fn quick_sort_in_place<T: Ord + Clone>(items: &mut [T]) {
166    let sorted = quick_sort(items);
167    items.clone_from_slice(&sorted);
168}
169
170/// Sorts `items` in place using stable merge sort.
171///
172/// Merge sort inherently needs O(n) auxiliary storage; this variant keeps that
173/// scratch internal instead of returning a new `Vec`.
174pub fn merge_sort_in_place<T: Ord + Clone>(items: &mut [T]) {
175    let sorted = merge_sort(items);
176    items.clone_from_slice(&sorted);
177}
178
179/// Sorts `items` in place using a binary max-heap. True in-place sort:
180/// no auxiliary buffer proportional to `items` is allocated.
181pub fn heap_sort_in_place<T: Ord>(items: &mut [T]) {
182    let n = items.len();
183    if n <= 1 {
184        return;
185    }
186    for i in (0..n / 2).rev() {
187        sift_down(items, n, i);
188    }
189    for end in (1..n).rev() {
190        items.swap(0, end);
191        sift_down(items, end, 0);
192    }
193}
194
195/// LSD radix sort for non-negative integers.
196pub fn radix_sort(items: &[i64]) -> Result<Vec<i64>, Error> {
197    if items.is_empty() {
198        return Ok(Vec::new());
199    }
200    let mut max_val = 0i64;
201    for &v in items {
202        if v < 0 {
203            return Err(Error::InvalidInput);
204        }
205        if v > max_val {
206            max_val = v;
207        }
208    }
209    let mut arr = items.to_vec();
210    let mut exp: i64 = 1;
211    while max_val / exp > 0 {
212        counting_sort_by_digit(&mut arr, exp);
213        // Stop before exp * 10 can overflow i64. No further digit pass is
214        // needed anyway: no i64 has a higher digit than exp's place.
215        if exp > i64::MAX / 10 {
216            break;
217        }
218        exp *= 10;
219    }
220    Ok(arr)
221}
222
223fn counting_sort_by_digit(arr: &mut [i64], exp: i64) {
224    let n = arr.len();
225    let mut output = vec![0; n];
226    let mut count = vec![0; 10];
227    for &num in arr.iter() {
228        count[((num / exp) % 10) as usize] += 1;
229    }
230    for i in 1..10 {
231        count[i] += count[i - 1];
232    }
233    for i in (0..n).rev() {
234        let index = ((arr[i] / exp) % 10) as usize;
235        output[count[index] - 1] = arr[i];
236        count[index] -= 1;
237    }
238    arr.copy_from_slice(&output);
239}
240
241/// Standard-library sort wrapper.
242///
243/// This delegates to the language's built-in `slice::sort`.
244pub fn native_sort<T: Ord + Clone>(items: &[T]) -> Vec<T> {
245    let mut arr = items.to_vec();
246    arr.sort();
247    arr
248}
249
250/// Sorts `items` in place using the standard library sort (`slice::sort`).
251pub fn native_sort_in_place<T: Ord>(items: &mut [T]) {
252    items.sort();
253}
254
255// ---------------- Searching ----------------
256
257fn is_sorted<T: Ord>(arr: &[T]) -> bool {
258    arr.windows(2).all(|w| w[0] <= w[1])
259}
260
261/// Binary search.
262///
263/// `arr` must be sorted in ascending order; this is a caller contract checked
264/// with `debug_assert!` in debug builds so searches stay O(log n). On unsorted
265/// input in release builds the result is unspecified (typically `NotFound`).
266///
267/// Returns `Ok(index)` of the target, or `Err(Error::NotFound)` if absent.
268///
269/// # Examples
270/// ```
271/// use algorithms_lib::binary_search;
272/// assert_eq!(binary_search(&[2, 4, 6, 8], &6), Ok(2));
273/// ```
274pub fn binary_search<T: Ord>(arr: &[T], target: &T) -> Result<usize, Error> {
275    debug_assert!(is_sorted(arr), "binary_search requires a sorted slice");
276    let mut lo = 0;
277    let mut hi = arr.len() as isize - 1;
278    while lo <= hi {
279        let mid = (lo + hi) / 2;
280        let mid_usize = mid as usize;
281        if &arr[mid_usize] == target {
282            return Ok(mid_usize);
283        }
284        if &arr[mid_usize] < target {
285            lo = mid + 1;
286        } else {
287            hi = mid - 1;
288        }
289    }
290    Err(Error::NotFound)
291}
292
293/// Binary search on answer for monotone predicates.
294///
295/// `low` and `high` are inclusive. `find` is `"minimum"` for the smallest
296/// satisfying value or `"maximum"` for the largest.
297pub fn binary_search_on_answer(
298    low: i64,
299    high: i64,
300    predicate: impl Fn(i64) -> bool,
301    find: &str,
302) -> Result<i64, Error> {
303    if find != "minimum" && find != "maximum" {
304        return Err(Error::InvalidInput);
305    }
306    if low > high {
307        return Err(Error::NotFound);
308    }
309    let mut left = low;
310    let mut right = high;
311    let mut answer: Option<i64> = None;
312    if find == "minimum" {
313        while left <= right {
314            let mid = left + (right - left) / 2;
315            if predicate(mid) {
316                answer = Some(mid);
317                right = mid - 1;
318            } else {
319                left = mid + 1;
320            }
321        }
322    } else {
323        while left <= right {
324            let mid = left + (right - left) / 2;
325            if predicate(mid) {
326                answer = Some(mid);
327                left = mid + 1;
328            } else {
329                right = mid - 1;
330            }
331        }
332    }
333    answer.ok_or(Error::NotFound)
334}
335
336/// Interpolation search for numeric data.
337///
338/// `arr` must be sorted in ascending order (caller contract, checked with
339/// `debug_assert!` in debug builds). Returns `Ok(index)` of the target, or
340/// `Err(Error::NotFound)` if absent.
341pub fn interpolation_search(arr: &[i64], target: i64) -> Result<usize, Error> {
342    debug_assert!(
343        is_sorted(arr),
344        "interpolation_search requires a sorted slice"
345    );
346    let mut lo = 0;
347    let mut hi = arr.len() as isize - 1;
348    while lo <= hi && target >= arr[lo as usize] && target <= arr[hi as usize] {
349        if arr[hi as usize] == arr[lo as usize] {
350            if arr[lo as usize] == target {
351                return Ok(lo as usize);
352            }
353            break;
354        }
355        let lo_usize = lo as usize;
356        let hi_usize = hi as usize;
357        // Use i128 for the intermediate products so extreme i64 values cannot
358        // overflow before the division.
359        let numerator = (target as i128 - arr[lo_usize] as i128) * ((hi_usize - lo_usize) as i128);
360        let denominator = arr[hi_usize] as i128 - arr[lo_usize] as i128;
361        let pos = lo_usize + (numerator / denominator) as usize;
362        if pos < lo_usize || pos > hi_usize {
363            break;
364        }
365        if arr[pos] == target {
366            return Ok(pos);
367        }
368        if arr[pos] < target {
369            lo = pos as isize + 1;
370        } else {
371            hi = pos as isize - 1;
372        }
373    }
374    Err(Error::NotFound)
375}
376
377/// Jump search.
378///
379/// `arr` must be sorted in ascending order (caller contract, checked with
380/// `debug_assert!` in debug builds). Returns `Ok(index)` of the target, or
381/// `Err(Error::NotFound)` if absent.
382pub fn jump_search<T: Ord>(arr: &[T], target: &T) -> Result<usize, Error> {
383    debug_assert!(is_sorted(arr), "jump_search requires a sorted slice");
384    let n = arr.len();
385    if n == 0 {
386        return Err(Error::NotFound);
387    }
388    let mut step = (n as f64).sqrt() as usize;
389    if step == 0 {
390        step = 1;
391    }
392    let mut prev = 0;
393    while arr[step.min(n) - 1] < *target {
394        prev = step;
395        step += (n as f64).sqrt() as usize;
396        if prev >= n {
397            return Err(Error::NotFound);
398        }
399    }
400    while arr[prev] < *target {
401        prev += 1;
402        if prev == step.min(n) {
403            return Err(Error::NotFound);
404        }
405    }
406    if &arr[prev] == target {
407        return Ok(prev);
408    }
409    Err(Error::NotFound)
410}
411
412// ---------------- Graphs ----------------
413
414/// Weighted edge for graph algorithms.
415#[derive(Debug, Clone, PartialEq)]
416pub struct Edge {
417    /// Destination vertex.
418    pub to: String,
419    /// Edge weight. `dijkstra`/`a_star` require `weight >= 0.0` and finite;
420    /// `bellman_ford` accepts negative weights.
421    pub weight: f64,
422}
423
424impl Edge {
425    /// Creates an edge to vertex `to` with the given `weight`.
426    pub fn new(to: &str, weight: f64) -> Self {
427        Edge {
428            to: to.to_string(),
429            weight,
430        }
431    }
432}
433
434/// Returns `Err(Error::InvalidGraph)` if any neighbor referenced in the
435/// adjacency map is not itself a key of the graph.
436fn validate_adjacency_list(graph: &HashMap<String, Vec<String>>) -> Result<(), Error> {
437    for neighbors in graph.values() {
438        for nbr in neighbors {
439            if !graph.contains_key(nbr) {
440                return Err(Error::InvalidGraph);
441            }
442        }
443    }
444    Ok(())
445}
446
447/// Breadth-first search.
448///
449/// Every neighbor listed in the adjacency map must also be a key of the map;
450/// missing keys return `Err(Error::InvalidGraph)`.
451pub fn bfs(graph: &HashMap<String, Vec<String>>, start: &str) -> Result<Vec<String>, Error> {
452    if !graph.contains_key(start) {
453        return Err(Error::InvalidGraph);
454    }
455    validate_adjacency_list(graph)?;
456    let mut visited = HashSet::new();
457    visited.insert(start.to_string());
458    let mut queue = VecDeque::new();
459    queue.push_back(start.to_string());
460    let mut result = Vec::new();
461    while let Some(current) = queue.pop_front() {
462        result.push(current.clone());
463        if let Some(neighbors) = graph.get(&current) {
464            for nbr in neighbors {
465                if visited.insert(nbr.clone()) {
466                    queue.push_back(nbr.clone());
467                }
468            }
469        }
470    }
471    Ok(result)
472}
473
474/// Depth-first search.
475///
476/// Every neighbor listed in the adjacency map must also be a key of the map;
477/// missing keys return `Err(Error::InvalidGraph)`.
478pub fn dfs(graph: &HashMap<String, Vec<String>>, start: &str) -> Result<Vec<String>, Error> {
479    if !graph.contains_key(start) {
480        return Err(Error::InvalidGraph);
481    }
482    validate_adjacency_list(graph)?;
483    let mut visited = HashSet::new();
484    visited.insert(start.to_string());
485    let mut stack = vec![start.to_string()];
486    let mut result = Vec::new();
487    while let Some(current) = stack.pop() {
488        result.push(current.clone());
489        if let Some(neighbors) = graph.get(&current) {
490            for nbr in neighbors.iter().rev() {
491                if visited.insert(nbr.clone()) {
492                    stack.push(nbr.clone());
493                }
494            }
495        }
496    }
497    Ok(result)
498}
499
500#[derive(Clone)]
501struct State {
502    cost: f64,
503    node: String,
504}
505
506impl PartialEq for State {
507    fn eq(&self, other: &Self) -> bool {
508        self.cost.total_cmp(&other.cost) == Ordering::Equal && self.node == other.node
509    }
510}
511
512impl Eq for State {}
513
514impl PartialOrd for State {
515    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
516        Some(self.cmp(other))
517    }
518}
519
520impl Ord for State {
521    fn cmp(&self, other: &Self) -> Ordering {
522        other
523            .cost
524            .total_cmp(&self.cost)
525            .then_with(|| self.node.cmp(&other.node))
526    }
527}
528
529/// Dijkstra's shortest paths.
530pub fn dijkstra(
531    graph: &HashMap<String, Vec<Edge>>,
532    start: &str,
533) -> Result<(HashMap<String, f64>, HashMap<String, String>), Error> {
534    if !graph.contains_key(start) {
535        return Err(Error::InvalidGraph);
536    }
537
538    // Pre-scan all edges so negative weights in unreachable components are
539    // detected, and collect every vertex referenced in the graph.
540    let mut all_nodes: HashSet<String> = graph.keys().cloned().collect();
541    let mut negative = false;
542    for edges in graph.values() {
543        for e in edges {
544            if e.weight < 0.0 || !e.weight.is_finite() {
545                negative = true;
546            }
547            all_nodes.insert(e.to.clone());
548        }
549    }
550    if negative {
551        return Err(Error::InvalidGraph);
552    }
553
554    let mut distances: HashMap<String, f64> = all_nodes
555        .iter()
556        .map(|k| (k.clone(), f64::INFINITY))
557        .collect();
558    distances.insert(start.to_string(), 0.0);
559    let mut predecessors = HashMap::new();
560
561    let mut pq = BinaryHeap::new();
562    pq.push(State {
563        cost: 0.0,
564        node: start.to_string(),
565    });
566
567    while let Some(State { cost, node }) = pq.pop() {
568        if cost > distances[&node] {
569            continue;
570        }
571        if let Some(edges) = graph.get(&node) {
572            for e in edges {
573                let nd = cost + e.weight;
574                let current = distances.get(&e.to).copied().unwrap_or(f64::INFINITY);
575                if nd < current {
576                    distances.insert(e.to.clone(), nd);
577                    predecessors.insert(e.to.clone(), node.clone());
578                    pq.push(State {
579                        cost: nd,
580                        node: e.to.clone(),
581                    });
582                }
583            }
584        }
585    }
586    Ok((distances, predecessors))
587}
588
589/// A* shortest path.
590///
591/// The heuristic must be non-negative. For an optimal first result it should be
592/// admissible and consistent; inconsistent but admissible heuristics still
593/// return an optimal path, but may cause nodes to be re-expanded.
594///
595/// All edge weights must be non-negative.
596pub fn a_star<F>(
597    graph: &HashMap<String, Vec<Edge>>,
598    start: &str,
599    goal: &str,
600    heuristic: F,
601) -> Result<(Vec<String>, f64), Error>
602where
603    F: Fn(&str, &str) -> f64,
604{
605    if !graph.contains_key(start) || !graph.contains_key(goal) {
606        return Err(Error::InvalidGraph);
607    }
608
609    let h = heuristic(start, goal);
610    if h < 0.0 {
611        return Err(Error::InvalidInput);
612    }
613
614    let mut g_score = HashMap::new();
615    g_score.insert(start.to_string(), 0.0);
616    let mut best_f: HashMap<String, f64> = HashMap::new();
617    best_f.insert(start.to_string(), h);
618    let mut came_from = HashMap::new();
619    let mut pq = BinaryHeap::new();
620    pq.push(State {
621        cost: h,
622        node: start.to_string(),
623    });
624
625    while let Some(State { cost, node }) = pq.pop() {
626        if let Some(&best) = best_f.get(&node) {
627            if cost > best {
628                continue;
629            }
630        }
631        if node == goal {
632            return Ok((reconstruct_path(&came_from, &node), g_score[goal]));
633        }
634        if let Some(edges) = graph.get(&node) {
635            for e in edges {
636                if e.weight < 0.0 || !e.weight.is_finite() {
637                    return Err(Error::InvalidGraph);
638                }
639                let h_nbr = heuristic(&e.to, goal);
640                if h_nbr < 0.0 {
641                    return Err(Error::InvalidInput);
642                }
643                let tentative = g_score[&node] + e.weight;
644                let current = g_score.get(&e.to).copied().unwrap_or(f64::INFINITY);
645                if tentative < current {
646                    came_from.insert(e.to.clone(), node.clone());
647                    g_score.insert(e.to.clone(), tentative);
648                    let f = tentative + h_nbr;
649                    let best = best_f.get(&e.to).copied().unwrap_or(f64::INFINITY);
650                    if f < best {
651                        best_f.insert(e.to.clone(), f);
652                        pq.push(State {
653                            cost: f,
654                            node: e.to.clone(),
655                        });
656                    }
657                }
658            }
659        }
660    }
661    Err(Error::NotFound)
662}
663
664fn reconstruct_path(came_from: &HashMap<String, String>, goal: &str) -> Vec<String> {
665    let mut current = goal.to_string();
666    let mut path = vec![current.clone()];
667    while let Some(prev) = came_from.get(&current) {
668        path.push(prev.clone());
669        current = prev.clone();
670    }
671    path.reverse();
672    path
673}
674
675struct Edge3 {
676    from: String,
677    to: String,
678    weight: f64,
679}
680
681/// Bellman-Ford shortest paths with negative cycle detection.
682pub fn bellman_ford(
683    graph: &HashMap<String, Vec<Edge>>,
684    start: &str,
685) -> Result<(HashMap<String, f64>, HashMap<String, String>), Error> {
686    if !graph.contains_key(start) {
687        return Err(Error::InvalidGraph);
688    }
689    let mut vertices = HashSet::new();
690    let mut edges = Vec::new();
691    for (u, list) in graph {
692        vertices.insert(u.clone());
693        for e in list {
694            vertices.insert(e.to.clone());
695            edges.push(Edge3 {
696                from: u.clone(),
697                to: e.to.clone(),
698                weight: e.weight,
699            });
700        }
701    }
702    let mut distances = HashMap::new();
703    distances.insert(start.to_string(), 0.0);
704    let mut predecessors = HashMap::new();
705
706    for _ in 0..vertices.len().saturating_sub(1) {
707        let mut updated = false;
708        for e in &edges {
709            if let Some(&du) = distances.get(&e.from) {
710                let dv = distances.get(&e.to).copied().unwrap_or(f64::INFINITY);
711                if du + e.weight < dv {
712                    distances.insert(e.to.clone(), du + e.weight);
713                    predecessors.insert(e.to.clone(), e.from.clone());
714                    updated = true;
715                }
716            }
717        }
718        if !updated {
719            break;
720        }
721    }
722
723    for e in &edges {
724        if let Some(&du) = distances.get(&e.from) {
725            let dv = distances.get(&e.to).copied().unwrap_or(f64::INFINITY);
726            if du + e.weight < dv {
727                return Err(Error::NegativeCycle);
728            }
729        }
730    }
731    Ok((distances, predecessors))
732}
733
734// ---------------- Dynamic programming ----------------
735
736/// 0/1 knapsack.
737///
738/// `capacity` and every weight must be non-negative and fit in `usize`
739/// (on 32-bit platforms values above `u32::MAX` are rejected instead of
740/// silently truncated).
741pub fn knapsack_01(weights: &[i64], values: &[i64], capacity: i64) -> Result<i64, Error> {
742    if weights.len() != values.len() {
743        return Err(Error::InvalidInput);
744    }
745    let cap = usize::try_from(capacity).map_err(|_| Error::InvalidInput)?;
746    let mut dp = vec![0; cap + 1];
747    for i in 0..weights.len() {
748        let w = usize::try_from(weights[i]).map_err(|_| Error::InvalidInput)?;
749        let v = values[i];
750        for c in (w..=cap).rev() {
751            if dp[c - w] + v > dp[c] {
752                dp[c] = dp[c - w] + v;
753            }
754        }
755    }
756    Ok(dp[cap])
757}
758
759/// Longest common subsequence length.
760pub fn longest_common_subsequence<T: PartialEq>(a: &[T], b: &[T]) -> usize {
761    let (m, n) = (a.len(), b.len());
762    let mut prev = vec![0; n + 1];
763    for i in 1..=m {
764        let mut curr = vec![0; n + 1];
765        for j in 1..=n {
766            if a[i - 1] == b[j - 1] {
767                curr[j] = prev[j - 1] + 1;
768            } else {
769                curr[j] = prev[j].max(curr[j - 1]);
770            }
771        }
772        prev = curr;
773    }
774    prev[n]
775}
776
777/// Levenshtein distance.
778pub fn edit_distance<T: PartialEq>(a: &[T], b: &[T]) -> usize {
779    let (m, n) = (a.len(), b.len());
780    let mut prev: Vec<usize> = (0..=n).collect();
781    for i in 1..=m {
782        let mut curr = vec![0; n + 1];
783        curr[0] = i;
784        for j in 1..=n {
785            let cost = if a[i - 1] == b[j - 1] { 0 } else { 1 };
786            let insertion = curr[j - 1] + 1;
787            let deletion = prev[j] + 1;
788            let substitution = prev[j - 1] + cost;
789            curr[j] = insertion.min(deletion).min(substitution);
790        }
791        prev = curr;
792    }
793    prev[n]
794}
795
796/// One step in an edit-distance script.
797#[derive(Debug, PartialEq, Clone)]
798pub enum EditOp<T: Clone + PartialEq> {
799    /// Keep the same symbol.
800    Match(T),
801    /// Delete a symbol from the source.
802    Delete(T),
803    /// Insert a symbol from the target.
804    Insert(T),
805    /// Replace one symbol with another.
806    Substitute {
807        /// Original symbol.
808        from: T,
809        /// Replacement symbol.
810        to: T,
811    },
812}
813
814/// Levenshtein distance and one optimal edit script.
815pub fn edit_distance_reconstruction<T: PartialEq + Clone>(
816    a: &[T],
817    b: &[T],
818) -> (usize, Vec<EditOp<T>>) {
819    let (m, n) = (a.len(), b.len());
820    let mut dp = vec![vec![0; n + 1]; m + 1];
821    for i in 0..=m {
822        dp[i][0] = i;
823    }
824    for j in 0..=n {
825        dp[0][j] = j;
826    }
827    for i in 1..=m {
828        for j in 1..=n {
829            if a[i - 1] == b[j - 1] {
830                dp[i][j] = dp[i - 1][j - 1];
831            } else {
832                dp[i][j] = 1 + dp[i - 1][j].min(dp[i][j - 1]).min(dp[i - 1][j - 1]);
833            }
834        }
835    }
836    let mut script = Vec::new();
837    let (mut i, mut j) = (m, n);
838    while i > 0 || j > 0 {
839        if i == 0 {
840            script.push(EditOp::Insert(b[j - 1].clone()));
841            j -= 1;
842        } else if j == 0 {
843            script.push(EditOp::Delete(a[i - 1].clone()));
844            i -= 1;
845        } else if a[i - 1] == b[j - 1] {
846            script.push(EditOp::Match(a[i - 1].clone()));
847            i -= 1;
848            j -= 1;
849        } else {
850            let best = dp[i][j];
851            if dp[i - 1][j - 1] + 1 == best {
852                script.push(EditOp::Substitute {
853                    from: a[i - 1].clone(),
854                    to: b[j - 1].clone(),
855                });
856                i -= 1;
857                j -= 1;
858            } else if dp[i][j - 1] + 1 == best {
859                script.push(EditOp::Insert(b[j - 1].clone()));
860                j -= 1;
861            } else {
862                script.push(EditOp::Delete(a[i - 1].clone()));
863                i -= 1;
864            }
865        }
866    }
867    script.reverse();
868    (dp[m][n], script)
869}
870
871/// Longest common subsequence length and one actual subsequence.
872pub fn longest_common_subsequence_reconstruction<T: PartialEq + Clone>(
873    a: &[T],
874    b: &[T],
875) -> (usize, Vec<T>) {
876    let (m, n) = (a.len(), b.len());
877    let mut dp = vec![vec![0; n + 1]; m + 1];
878    for i in 1..=m {
879        for j in 1..=n {
880            if a[i - 1] == b[j - 1] {
881                dp[i][j] = dp[i - 1][j - 1] + 1;
882            } else {
883                dp[i][j] = dp[i - 1][j].max(dp[i][j - 1]);
884            }
885        }
886    }
887    let mut result = Vec::with_capacity(dp[m][n]);
888    let (mut i, mut j) = (m, n);
889    while i > 0 && j > 0 {
890        if a[i - 1] == b[j - 1] {
891            result.push(a[i - 1].clone());
892            i -= 1;
893            j -= 1;
894        } else if dp[i - 1][j] >= dp[i][j - 1] {
895            i -= 1;
896        } else {
897            j -= 1;
898        }
899    }
900    result.reverse();
901    (dp[m][n], result)
902}
903
904// ---------------- String algorithms ----------------
905
906/// Knuth-Morris-Pratt pattern matching.
907///
908/// Returns all starting **byte offsets** of `pattern` in `text`, including
909/// overlapping occurrences. Returns `Err(Error::InvalidInput)` when `pattern`
910/// is empty.
911pub fn kmp_search(text: &str, pattern: &str) -> Result<Vec<usize>, Error> {
912    if pattern.is_empty() {
913        return Err(Error::InvalidInput);
914    }
915    let t = text.as_bytes();
916    let p = pattern.as_bytes();
917    let failure = compute_kmp_failure(p);
918    let mut matches = Vec::new();
919    let mut j = 0;
920    for i in 0..t.len() {
921        while j > 0 && t[i] != p[j] {
922            j = failure[j - 1];
923        }
924        if t[i] == p[j] {
925            j += 1;
926        }
927        if j == p.len() {
928            matches.push(i + 1 - p.len());
929            j = failure[j - 1];
930        }
931    }
932    Ok(matches)
933}
934
935fn compute_kmp_failure(pattern: &[u8]) -> Vec<usize> {
936    let mut failure = vec![0; pattern.len()];
937    let mut j = 0;
938    for i in 1..pattern.len() {
939        while j > 0 && pattern[i] != pattern[j] {
940            j = failure[j - 1];
941        }
942        if pattern[i] == pattern[j] {
943            j += 1;
944            failure[i] = j;
945        }
946    }
947    failure
948}
949
950/// Rolling-hash pattern matching.
951///
952/// Returns all starting **byte offsets** of `pattern` in `text`, including
953/// overlapping occurrences.
954///
955/// `base` and `modulus` must be positive; `pattern` must not be empty —
956/// violations return `Err(Error::InvalidInput)`. The arithmetic is performed
957/// in `i128` so large `base`/`modulus` values do not overflow.
958pub fn rabin_karp_search(
959    text: &str,
960    pattern: &str,
961    base: i64,
962    modulus: i64,
963) -> Result<Vec<usize>, Error> {
964    if pattern.is_empty() {
965        return Err(Error::InvalidInput);
966    }
967    if modulus <= 0 {
968        return Err(Error::InvalidInput);
969    }
970    if base <= 0 {
971        return Err(Error::InvalidInput);
972    }
973
974    let t = text.as_bytes();
975    let p = pattern.as_bytes();
976    let (n, m) = (t.len(), p.len());
977    if m > n {
978        return Ok(Vec::new());
979    }
980
981    let base = base as i128;
982    let modulus = modulus as i128;
983
984    let mut h = 1i128;
985    for _ in 0..m - 1 {
986        h = (h * base) % modulus;
987    }
988    let mut pattern_hash = 0i128;
989    let mut text_hash = 0i128;
990    for i in 0..m {
991        pattern_hash = (pattern_hash * base + p[i] as i128) % modulus;
992        text_hash = (text_hash * base + t[i] as i128) % modulus;
993    }
994    let mut matches = Vec::new();
995    for i in 0..=n - m {
996        if text_hash == pattern_hash && &t[i..i + m] == p {
997            matches.push(i);
998        }
999        if i < n - m {
1000            text_hash = (text_hash - (t[i] as i128) * h).rem_euclid(modulus);
1001            text_hash = (text_hash * base + t[i + m] as i128).rem_euclid(modulus);
1002        }
1003    }
1004    Ok(matches)
1005}
1006
1007/// Boyer-Moore pattern matching with the bad-character and good-suffix rules.
1008///
1009/// Returns all starting **byte offsets** of `pattern` in `text`, including
1010/// overlapping occurrences. Returns `Err(Error::InvalidInput)` when `pattern`
1011/// is empty.
1012pub fn boyer_moore_search(text: &str, pattern: &str) -> Result<Vec<usize>, Error> {
1013    if pattern.is_empty() {
1014        return Err(Error::InvalidInput);
1015    }
1016    let t = text.as_bytes();
1017    let p = pattern.as_bytes();
1018    let (n, m) = (t.len(), p.len());
1019    if m > n {
1020        return Ok(Vec::new());
1021    }
1022    let mut bad_char = HashMap::new();
1023    for (i, &b) in p.iter().enumerate() {
1024        bad_char.insert(b, i);
1025    }
1026    let good_suffix = good_suffix_shifts(p);
1027    let mut matches = Vec::new();
1028    let mut i = 0usize;
1029    while i <= n - m {
1030        let mut j = m as isize - 1;
1031        while j >= 0 && t[i + j as usize] == p[j as usize] {
1032            j -= 1;
1033        }
1034        if j < 0 {
1035            matches.push(i);
1036            i += good_suffix[0];
1037        } else {
1038            let last = bad_char
1039                .get(&t[i + j as usize])
1040                .map(|&l| l as isize)
1041                .unwrap_or(-1);
1042            i += (good_suffix[j as usize + 1] as isize).max(j - last).max(1) as usize;
1043        }
1044    }
1045    Ok(matches)
1046}
1047
1048/// Builds the Boyer-Moore good-suffix table: `shift[k]` is the shift applied
1049/// after a mismatch at pattern index `k - 1`, and `shift[0]` is the
1050/// border-based shift applied after a full match so that overlapping matches
1051/// are still reported.
1052fn good_suffix_shifts(pattern: &[u8]) -> Vec<usize> {
1053    let m = pattern.len();
1054    let mut shift = vec![0usize; m + 1];
1055    let mut border_pos = vec![0usize; m + 1];
1056    let (mut i, mut j) = (m, m + 1);
1057    border_pos[i] = j;
1058    while i > 0 {
1059        while j <= m && pattern[i - 1] != pattern[j - 1] {
1060            if shift[j] == 0 {
1061                shift[j] = j - i;
1062            }
1063            j = border_pos[j];
1064        }
1065        i -= 1;
1066        j -= 1;
1067        border_pos[i] = j;
1068    }
1069    let mut j = border_pos[0];
1070    for i in 0..=m {
1071        if shift[i] == 0 {
1072            shift[i] = j;
1073        }
1074        if i == j {
1075            j = border_pos[j];
1076        }
1077    }
1078    shift
1079}
1080
1081// ---------------- Greedy ----------------
1082
1083/// Maximum compatible activity indices.
1084pub fn activity_selection(activities: &[(i64, i64)]) -> Result<Vec<usize>, Error> {
1085    if activities.is_empty() {
1086        return Ok(Vec::new());
1087    }
1088    #[derive(Clone, Copy)]
1089    struct Indexed {
1090        index: usize,
1091        start: i64,
1092        end: i64,
1093    }
1094    let mut list = Vec::with_capacity(activities.len());
1095    for (i, &(start, end)) in activities.iter().enumerate() {
1096        if start > end {
1097            return Err(Error::InvalidInput);
1098        }
1099        list.push(Indexed {
1100            index: i,
1101            start,
1102            end,
1103        });
1104    }
1105    list.sort_by_key(|a| a.end);
1106    let mut selected = vec![list[0].index];
1107    let mut last_end = list[0].end;
1108    for item in list.iter().skip(1) {
1109        if item.start >= last_end {
1110            selected.push(item.index);
1111            last_end = item.end;
1112        }
1113    }
1114    Ok(selected)
1115}
1116
1117/// Fractional knapsack maximum value.
1118///
1119/// Negative or NaN weights and NaN values are rejected with
1120/// `Err(Error::InvalidInput)`; a zero-weight item contributes its full value
1121/// when that value is positive, since it consumes no capacity. Items with a
1122/// non-positive value-to-weight ratio are never taken.
1123pub fn fractional_knapsack(weights: &[f64], values: &[f64], capacity: f64) -> Result<f64, Error> {
1124    if weights.len() != values.len() {
1125        return Err(Error::InvalidInput);
1126    }
1127    if capacity < 0.0 {
1128        return Err(Error::InvalidInput);
1129    }
1130    #[derive(Clone, Copy)]
1131    struct Item {
1132        ratio: f64,
1133        weight: f64,
1134    }
1135    let mut items = Vec::with_capacity(weights.len());
1136    let mut total = 0.0;
1137    for i in 0..weights.len() {
1138        if weights[i] < 0.0 || weights[i].is_nan() || values[i].is_nan() {
1139            return Err(Error::InvalidInput);
1140        }
1141        if weights[i] == 0.0 {
1142            // Zero-weight items consume no capacity: take them if they add value.
1143            total += values[i].max(0.0);
1144            continue;
1145        }
1146        items.push(Item {
1147            ratio: values[i] / weights[i],
1148            weight: weights[i],
1149        });
1150    }
1151    items.sort_by(|a, b| b.ratio.partial_cmp(&a.ratio).unwrap_or(Ordering::Equal));
1152    let mut remaining = capacity;
1153    for it in items {
1154        if it.ratio <= 0.0 {
1155            break; // taking a non-positive-value item can only lower the total
1156        }
1157        let take = it.weight.min(remaining);
1158        total += take * it.ratio;
1159        remaining -= take;
1160        if remaining <= 0.0 {
1161            break;
1162        }
1163    }
1164    Ok(total)
1165}
1166
1167struct HuffmanNode {
1168    freq: i64,
1169    char: String,
1170    left: Option<Box<HuffmanNode>>,
1171    right: Option<Box<HuffmanNode>>,
1172}
1173
1174impl PartialEq for HuffmanNode {
1175    fn eq(&self, other: &Self) -> bool {
1176        self.freq == other.freq && self.char == other.char
1177    }
1178}
1179
1180impl Eq for HuffmanNode {}
1181
1182impl PartialOrd for HuffmanNode {
1183    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1184        Some(self.cmp(other))
1185    }
1186}
1187
1188impl Ord for HuffmanNode {
1189    fn cmp(&self, other: &Self) -> Ordering {
1190        self.freq
1191            .cmp(&other.freq)
1192            .then_with(|| self.char.cmp(&other.char))
1193    }
1194}
1195
1196/// Huffman coding.
1197///
1198/// Every frequency must be positive; violations return
1199/// `Err(Error::InvalidInput)`. Symbol keys may be any string, including the
1200/// empty string.
1201pub fn huffman_coding(
1202    frequencies: &HashMap<String, i64>,
1203) -> Result<HashMap<String, String>, Error> {
1204    if frequencies.is_empty() {
1205        return Err(Error::InvalidInput);
1206    }
1207    if frequencies.values().any(|&f| f <= 0) {
1208        return Err(Error::InvalidInput);
1209    }
1210    let mut pq = BinaryHeap::new();
1211    for (ch, &freq) in frequencies {
1212        pq.push(Reverse(HuffmanNode {
1213            freq,
1214            char: ch.clone(),
1215            left: None,
1216            right: None,
1217        }));
1218    }
1219    if pq.len() == 1 {
1220        let node = pq.pop().unwrap().0;
1221        let mut codes = HashMap::new();
1222        codes.insert(node.char, "0".to_string());
1223        return Ok(codes);
1224    }
1225    while pq.len() > 1 {
1226        let left = pq.pop().unwrap().0;
1227        let right = pq.pop().unwrap().0;
1228        let parent = HuffmanNode {
1229            freq: left.freq + right.freq,
1230            char: String::new(),
1231            left: Some(Box::new(left)),
1232            right: Some(Box::new(right)),
1233        };
1234        pq.push(Reverse(parent));
1235    }
1236    let root = pq.pop().unwrap().0;
1237    let mut codes = HashMap::new();
1238    assign_huffman_codes(&root, "", &mut codes);
1239    Ok(codes)
1240}
1241
1242fn assign_huffman_codes(node: &HuffmanNode, prefix: &str, codes: &mut HashMap<String, String>) {
1243    // Internal nodes always have both children; leaves have neither. This also
1244    // lets the empty string be used as a symbol key.
1245    if node.left.is_none() && node.right.is_none() {
1246        if prefix.is_empty() {
1247            codes.insert(node.char.clone(), "0".to_string());
1248        } else {
1249            codes.insert(node.char.clone(), prefix.to_string());
1250        }
1251        return;
1252    }
1253    if let Some(ref left) = node.left {
1254        assign_huffman_codes(left, &format!("{}0", prefix), codes);
1255    }
1256    if let Some(ref right) = node.right {
1257        assign_huffman_codes(right, &format!("{}1", prefix), codes);
1258    }
1259}
1260
1261/// Encode a sequence of symbols using a Huffman code table.
1262pub fn huffman_encode(
1263    symbols: &[String],
1264    code_table: &HashMap<String, String>,
1265) -> Result<String, Error> {
1266    let mut parts = Vec::with_capacity(symbols.len());
1267    for s in symbols {
1268        let code = code_table.get(s).ok_or_else(|| Error::InvalidInput)?;
1269        parts.push(code.clone());
1270    }
1271    Ok(parts.concat())
1272}
1273
1274/// Decode a Huffman bit string using a code table.
1275pub fn huffman_decode(
1276    encoded: &str,
1277    code_table: &HashMap<String, String>,
1278) -> Result<Vec<String>, Error> {
1279    let mut reverse = HashMap::with_capacity(code_table.len());
1280    for (s, code) in code_table {
1281        reverse.insert(code.clone(), s.clone());
1282    }
1283    let mut result = Vec::new();
1284    let mut current = String::new();
1285    for bit in encoded.chars() {
1286        current.push(bit);
1287        if let Some(s) = reverse.get(&current) {
1288            result.push(s.clone());
1289            current.clear();
1290        }
1291    }
1292    if !current.is_empty() {
1293        return Err(Error::InvalidInput);
1294    }
1295    Ok(result)
1296}
1297
1298// ---------------- Divide and conquer ----------------
1299
1300/// Maximum subarray sum.
1301pub fn max_subarray(arr: &[i64]) -> Result<i64, Error> {
1302    if arr.is_empty() {
1303        return Err(Error::InvalidInput);
1304    }
1305    Ok(max_subarray_dc(arr, 0, arr.len() - 1))
1306}
1307
1308fn max_subarray_dc(arr: &[i64], left: usize, right: usize) -> i64 {
1309    if left == right {
1310        return arr[left];
1311    }
1312    let mid = (left + right) / 2;
1313    let left_sum = max_subarray_dc(arr, left, mid);
1314    let right_sum = max_subarray_dc(arr, mid + 1, right);
1315    let cross_sum = max_crossing_sum(arr, left, mid, right);
1316    left_sum.max(right_sum).max(cross_sum)
1317}
1318
1319fn max_crossing_sum(arr: &[i64], left: usize, mid: usize, right: usize) -> i64 {
1320    let mut sum = arr[mid];
1321    let mut left_max = arr[mid];
1322    for i in (left..mid).rev() {
1323        sum += arr[i];
1324        if sum > left_max {
1325            left_max = sum;
1326        }
1327    }
1328    sum = arr[mid + 1];
1329    let mut right_max = arr[mid + 1];
1330    for i in mid + 2..=right {
1331        sum += arr[i];
1332        if sum > right_max {
1333            right_max = sum;
1334        }
1335    }
1336    left_max + right_max
1337}
1338
1339/// Inversion count.
1340pub fn count_inversions<T: Ord + Clone>(arr: &[T]) -> usize {
1341    let (_, count) = sort_and_count(arr);
1342    count
1343}
1344
1345fn sort_and_count<T: Ord + Clone>(arr: &[T]) -> (Vec<T>, usize) {
1346    let n = arr.len();
1347    if n <= 1 {
1348        return (arr.to_vec(), 0);
1349    }
1350    let mid = n / 2;
1351    let (left, lcount) = sort_and_count(&arr[..mid]);
1352    let (right, rcount) = sort_and_count(&arr[mid..]);
1353    let (merged, scount) = merge_and_count(&left, &right);
1354    (merged, lcount + rcount + scount)
1355}
1356
1357fn merge_and_count<T: Ord + Clone>(left: &[T], right: &[T]) -> (Vec<T>, usize) {
1358    let mut merged = Vec::with_capacity(left.len() + right.len());
1359    let mut count = 0;
1360    let mut i = 0;
1361    let mut j = 0;
1362    while i < left.len() && j < right.len() {
1363        if left[i] <= right[j] {
1364            merged.push(left[i].clone());
1365            i += 1;
1366        } else {
1367            merged.push(right[j].clone());
1368            j += 1;
1369            count += left.len() - i;
1370        }
1371    }
1372    merged.extend_from_slice(&left[i..]);
1373    merged.extend_from_slice(&right[j..]);
1374    (merged, count)
1375}
1376
1377/// Exponentiation by squaring. Returns `Err(Error::InvalidInput)` when `base`
1378/// is zero and `exponent` is negative. By convention, `0^0` returns `1.0`.
1379pub fn fast_power(base: f64, exponent: i64) -> Result<f64, Error> {
1380    if base == 0.0 && exponent < 0 {
1381        return Err(Error::InvalidInput);
1382    }
1383    // unsigned_abs yields |exponent| as u64 and is correct even for i64::MIN,
1384    // whose negation cannot be represented in i64.
1385    let mut exp = exponent.unsigned_abs();
1386    let mut result = 1.0f64;
1387    let mut b = base;
1388    while exp > 0 {
1389        if exp & 1 == 1 {
1390            result *= b;
1391        }
1392        b *= b;
1393        exp >>= 1;
1394    }
1395    if exponent < 0 {
1396        result = 1.0 / result;
1397    }
1398    Ok(result)
1399}
1400
1401/// Greatest common divisor.
1402///
1403/// The result is always non-negative. Returns [`Error::InvalidInput`] if the
1404/// result does not fit in an `i64` (this only happens for `gcd(i64::MIN, 0)`).
1405pub fn gcd(a: i64, b: i64) -> Result<i64, Error> {
1406    let mut a = a.unsigned_abs();
1407    let mut b = b.unsigned_abs();
1408    while b != 0 {
1409        (a, b) = (b, a % b);
1410    }
1411    if a > i64::MAX as u64 {
1412        return Err(Error::InvalidInput);
1413    }
1414    Ok(a as i64)
1415}
1416
1417/// Least common multiple.
1418///
1419/// Returns [`Error::InvalidInput`] if both inputs are zero or if the result
1420/// overflows an `i64`.
1421pub fn lcm(a: i64, b: i64) -> Result<i64, Error> {
1422    if a == 0 && b == 0 {
1423        return Err(Error::InvalidInput);
1424    }
1425    let g = gcd(a, b)?;
1426    let a_div = (a / g) as i128;
1427    let b_i = b as i128;
1428    let result = (a_div * b_i).abs();
1429    if result > i64::MAX as i128 {
1430        return Err(Error::InvalidInput);
1431    }
1432    Ok(result as i64)
1433}
1434
1435/// Modular exponentiation by squaring: `(base^exponent) % mod`.
1436///
1437/// Returns [`Error::InvalidInput`] if `mod` is not positive or `exponent`
1438/// is negative.
1439pub fn modular_fast_power(base: i64, exponent: i64, modulus: i64) -> Result<i64, Error> {
1440    if modulus <= 0 {
1441        return Err(Error::InvalidInput);
1442    }
1443    if exponent < 0 {
1444        return Err(Error::InvalidInput);
1445    }
1446    if modulus == 1 {
1447        return Ok(0);
1448    }
1449    let m = modulus as i128;
1450    let mut b = (base % modulus) as i128;
1451    if b < 0 {
1452        b += m;
1453    }
1454    let mut result: i128 = 1;
1455    let mut exp = exponent as u64;
1456    while exp > 0 {
1457        if exp & 1 == 1 {
1458            result = (result * b) % m;
1459        }
1460        b = (b * b) % m;
1461        exp >>= 1;
1462    }
1463    Ok(result as i64)
1464}
1465
1466/// Returns the k-th smallest element (0-indexed) of `items`.
1467///
1468/// Returns [`Error::InvalidInput`] if `k` is out of range.
1469pub fn quick_select<T: Ord + Clone>(items: &[T], k: usize) -> Result<T, Error> {
1470    if k >= items.len() {
1471        return Err(Error::InvalidInput);
1472    }
1473    let mut arr = items.to_vec();
1474    let mut k = k;
1475    loop {
1476        if arr.len() == 1 {
1477            return Ok(arr[0].clone());
1478        }
1479        let pivot = arr[arr.len() / 2].clone();
1480        let mut lows = Vec::new();
1481        let mut pivots = Vec::new();
1482        let mut highs = Vec::new();
1483        for x in &arr {
1484            match x.cmp(&pivot) {
1485                Ordering::Less => lows.push(x.clone()),
1486                Ordering::Greater => highs.push(x.clone()),
1487                Ordering::Equal => pivots.push(x.clone()),
1488            }
1489        }
1490        if k < lows.len() {
1491            arr = lows;
1492        } else if k < lows.len() + pivots.len() {
1493            return Ok(pivot);
1494        } else {
1495            k -= lows.len() + pivots.len();
1496            arr = highs;
1497        }
1498    }
1499}
1500
1501/// Sieve of Eratosthenes. Returns all prime numbers `<= n`.
1502pub fn sieve_of_eratosthenes(n: i64) -> Result<Vec<i64>, Error> {
1503    if n < 0 {
1504        return Err(Error::InvalidInput);
1505    }
1506    let n = n as usize;
1507    if n < 2 {
1508        return Ok(Vec::new());
1509    }
1510    let mut sieve = vec![true; n + 1];
1511    sieve[0] = false;
1512    sieve[1] = false;
1513    let limit = (n as f64).sqrt() as usize;
1514    for p in 2..=limit {
1515        if sieve[p] {
1516            let mut multiple = p * p;
1517            while multiple <= n {
1518                sieve[multiple] = false;
1519                multiple += p;
1520            }
1521        }
1522    }
1523    Ok(sieve
1524        .iter()
1525        .enumerate()
1526        .filter(|(_, &is_prime)| is_prime)
1527        .map(|(i, _)| i as i64)
1528        .collect())
1529}
1530
1531/// Topological sort using Kahn's algorithm.
1532pub fn topological_sort(graph: &HashMap<String, Vec<String>>) -> Result<Vec<String>, Error> {
1533    validate_adjacency_list(graph)?;
1534    let mut in_degree: HashMap<String, usize> = HashMap::new();
1535    for (node, _) in graph {
1536        in_degree.insert(node.clone(), 0);
1537    }
1538    for (_, neighbors) in graph {
1539        for nbr in neighbors {
1540            *in_degree.get_mut(nbr).unwrap() += 1;
1541        }
1542    }
1543
1544    let mut queue: VecDeque<String> = in_degree
1545        .iter()
1546        .filter(|(_, &deg)| deg == 0)
1547        .map(|(node, _)| node.clone())
1548        .collect();
1549
1550    let mut order = Vec::new();
1551    while let Some(node) = queue.pop_front() {
1552        order.push(node.clone());
1553        for nbr in graph.get(&node).unwrap_or(&Vec::new()) {
1554            let deg = in_degree.get_mut(nbr).unwrap();
1555            *deg -= 1;
1556            if *deg == 0 {
1557                queue.push_back(nbr.clone());
1558            }
1559        }
1560    }
1561
1562    if order.len() != in_degree.len() {
1563        return Err(Error::InvalidGraph);
1564    }
1565    Ok(order)
1566}
1567
1568/// Disjoint-set union–find with union by rank and path compression.
1569pub struct UnionFind<T: Hash + Eq + Clone> {
1570    parent: HashMap<T, T>,
1571    rank: HashMap<T, u32>,
1572}
1573
1574impl<T: Hash + Eq + Clone> UnionFind<T> {
1575    /// Create a new union–find structure for the given elements.
1576    pub fn new(elements: impl IntoIterator<Item = T>) -> Self {
1577        let mut parent = HashMap::new();
1578        let mut rank = HashMap::new();
1579        for x in elements {
1580            parent.insert(x.clone(), x.clone());
1581            rank.insert(x, 0);
1582        }
1583        Self { parent, rank }
1584    }
1585
1586    /// Return the representative of the set containing `x`.
1587    pub fn find(&mut self, x: &T) -> Result<T, Error> {
1588        if !self.parent.contains_key(x) {
1589            return Err(Error::InvalidInput);
1590        }
1591        let mut root = x.clone();
1592        while self.parent[&root] != root {
1593            root = self.parent[&root].clone();
1594        }
1595        let mut node = x.clone();
1596        while self.parent[&node] != root {
1597            let parent = self.parent[&node].clone();
1598            self.parent.insert(node.clone(), root.clone());
1599            node = parent;
1600        }
1601        Ok(root)
1602    }
1603
1604    /// Merge the sets containing `x` and `y`.
1605    pub fn union(&mut self, x: &T, y: &T) -> Result<bool, Error> {
1606        let root_x = self.find(x)?;
1607        let root_y = self.find(y)?;
1608        if root_x == root_y {
1609            return Ok(false);
1610        }
1611        let rank_x = *self.rank.get(&root_x).unwrap_or(&0);
1612        let rank_y = *self.rank.get(&root_y).unwrap_or(&0);
1613        if rank_x < rank_y {
1614            self.parent.insert(root_x, root_y);
1615        } else if rank_x > rank_y {
1616            self.parent.insert(root_y, root_x);
1617        } else {
1618            self.parent.insert(root_y, root_x.clone());
1619            self.rank.insert(root_x, rank_x + 1);
1620        }
1621        Ok(true)
1622    }
1623
1624    /// Return whether `x` and `y` are in the same set.
1625    pub fn connected(&mut self, x: &T, y: &T) -> Result<bool, Error> {
1626        Ok(self.find(x)? == self.find(y)?)
1627    }
1628
1629    /// Return the current number of disjoint sets.
1630    pub fn count(&mut self) -> usize {
1631        let keys: Vec<T> = self.parent.keys().cloned().collect();
1632        let mut roots = HashSet::new();
1633        for k in keys {
1634            if let Ok(root) = self.find(&k) {
1635                roots.insert(root);
1636            }
1637        }
1638        roots.len()
1639    }
1640}
1641
1642/// A min-priority queue.
1643pub struct PriorityQueue<T: Ord> {
1644    heap: BinaryHeap<Reverse<T>>,
1645}
1646
1647impl<T: Ord> PriorityQueue<T> {
1648    /// Create a new priority queue with the given items.
1649    pub fn new(items: impl IntoIterator<Item = T>) -> Self {
1650        let heap: BinaryHeap<Reverse<T>> = items.into_iter().map(Reverse).collect();
1651        Self { heap }
1652    }
1653
1654    /// Add an item.
1655    pub fn push(&mut self, item: T) {
1656        self.heap.push(Reverse(item));
1657    }
1658
1659    /// Remove and return the smallest item.
1660    pub fn pop(&mut self) -> Result<T, Error> {
1661        self.heap.pop().map(|r| r.0).ok_or(Error::InvalidInput)
1662    }
1663
1664    /// Return the smallest item without removing it.
1665    pub fn peek(&self) -> Result<&T, Error> {
1666        self.heap.peek().map(|r| &r.0).ok_or(Error::InvalidInput)
1667    }
1668
1669    /// Return the number of items.
1670    pub fn len(&self) -> usize {
1671        self.heap.len()
1672    }
1673
1674    /// Return whether the queue is empty.
1675    pub fn is_empty(&self) -> bool {
1676        self.heap.is_empty()
1677    }
1678}
1679
1680/// An undirected, weighted edge for minimum spanning tree algorithms.
1681#[derive(Clone)]
1682pub struct MSTEdge {
1683    /// One endpoint of the edge.
1684    pub u: String,
1685    /// The other endpoint of the edge.
1686    pub v: String,
1687    /// Edge weight.
1688    pub weight: f64,
1689}
1690
1691/// Kruskal's minimum spanning tree (or forest) algorithm.
1692pub fn minimum_spanning_tree(edges: &[MSTEdge]) -> Result<(f64, Vec<MSTEdge>), Error> {
1693    for e in edges {
1694        if !e.weight.is_finite() {
1695            return Err(Error::InvalidInput);
1696        }
1697    }
1698
1699    let mut sorted: Vec<MSTEdge> = edges.to_vec();
1700    sorted.sort_by(|a, b| a.weight.partial_cmp(&b.weight).unwrap_or(Ordering::Equal));
1701
1702    let mut vertices = HashSet::new();
1703    for e in edges {
1704        vertices.insert(e.u.clone());
1705        vertices.insert(e.v.clone());
1706    }
1707    let mut uf = UnionFind::new(Vec::new());
1708    for v in vertices {
1709        uf.parent.insert(v.clone(), v.clone());
1710        uf.rank.insert(v, 0);
1711    }
1712
1713    let mut total = 0.0;
1714    let mut mst = Vec::new();
1715    for e in sorted {
1716        if e.u == e.v {
1717            continue;
1718        }
1719        if uf.union(&e.u, &e.v)? {
1720            total += e.weight;
1721            mst.push(e);
1722        }
1723    }
1724    Ok((total, mst))
1725}
1726
1727/// Floyd–Warshall all-pairs shortest paths.
1728pub fn floyd_warshall(
1729    graph: &HashMap<String, Vec<Edge>>,
1730) -> Result<HashMap<String, HashMap<String, f64>>, Error> {
1731    let mut all_nodes = HashSet::new();
1732    for u in graph.keys() {
1733        all_nodes.insert(u.clone());
1734    }
1735    for edges in graph.values() {
1736        for e in edges {
1737            all_nodes.insert(e.to.clone());
1738        }
1739    }
1740    for (_u, edges) in graph {
1741        for e in edges {
1742            if !all_nodes.contains(&e.to) {
1743                return Err(Error::InvalidGraph);
1744            }
1745            if !e.weight.is_finite() {
1746                return Err(Error::InvalidInput);
1747            }
1748        }
1749    }
1750
1751    let inf = f64::INFINITY;
1752    let mut dist: HashMap<String, HashMap<String, f64>> = HashMap::new();
1753    for u in &all_nodes {
1754        let mut row = HashMap::new();
1755        for v in &all_nodes {
1756            row.insert(v.clone(), if u == v { 0.0 } else { inf });
1757        }
1758        dist.insert(u.clone(), row);
1759    }
1760    for (u, edges) in graph {
1761        for e in edges {
1762            if e.weight < dist[u][&e.to] {
1763                dist.get_mut(u).unwrap().insert(e.to.clone(), e.weight);
1764            }
1765        }
1766    }
1767
1768    for k in &all_nodes {
1769        for i in &all_nodes {
1770            let dik = dist[i][k];
1771            if dik.is_infinite() {
1772                continue;
1773            }
1774            for j in &all_nodes {
1775                let nd = dik + dist[k][j];
1776                if nd < dist[i][j] {
1777                    dist.get_mut(i).unwrap().insert(j.clone(), nd);
1778                }
1779            }
1780        }
1781    }
1782
1783    for u in &all_nodes {
1784        if dist[u][u] < 0.0 {
1785            return Err(Error::NegativeCycle);
1786        }
1787    }
1788    Ok(dist)
1789}
1790
1791/// Kosaraju's strongly connected components algorithm.
1792pub fn strongly_connected_components(
1793    graph: &HashMap<String, Vec<String>>,
1794) -> Result<Vec<Vec<String>>, Error> {
1795    validate_adjacency_list(graph)?;
1796
1797    let mut visited = HashSet::new();
1798    let mut order: Vec<String> = Vec::new();
1799
1800    for u in graph.keys() {
1801        if !visited.contains(u) {
1802            let mut stack: Vec<(String, bool)> = vec![(u.clone(), false)];
1803            while let Some((node, processed)) = stack.pop() {
1804                if processed {
1805                    order.push(node);
1806                    continue;
1807                }
1808                if visited.contains(&node) {
1809                    continue;
1810                }
1811                visited.insert(node.clone());
1812                stack.push((node.clone(), true));
1813                if let Some(neighbors) = graph.get(&node) {
1814                    for nbr in neighbors.iter().rev() {
1815                        if !visited.contains(nbr) {
1816                            stack.push((nbr.clone(), false));
1817                        }
1818                    }
1819                }
1820            }
1821        }
1822    }
1823
1824    let mut reverse: HashMap<String, Vec<String>> = HashMap::new();
1825    for u in graph.keys() {
1826        reverse.insert(u.clone(), Vec::new());
1827    }
1828    for (u, neighbors) in graph {
1829        for v in neighbors {
1830            reverse.get_mut(v).unwrap().push(u.clone());
1831        }
1832    }
1833
1834    let mut visited = HashSet::new();
1835    let mut components: Vec<Vec<String>> = Vec::new();
1836
1837    for u in order.iter().rev() {
1838        if !visited.contains(u) {
1839            let mut component = Vec::new();
1840            let mut stack = vec![u.clone()];
1841            while let Some(node) = stack.pop() {
1842                if visited.contains(&node) {
1843                    continue;
1844                }
1845                visited.insert(node.clone());
1846                component.push(node.clone());
1847                if let Some(neighbors) = reverse.get(&node) {
1848                    for nbr in neighbors {
1849                        if !visited.contains(nbr) {
1850                            stack.push(nbr.clone());
1851                        }
1852                    }
1853                }
1854            }
1855            components.push(component);
1856        }
1857    }
1858    Ok(components)
1859}
1860
1861/// Returns whether the directed graph contains a cycle.
1862pub fn has_cycle(graph: &HashMap<String, Vec<String>>) -> Result<bool, Error> {
1863    validate_adjacency_list(graph)?;
1864
1865    let mut state: HashMap<String, i8> = HashMap::new();
1866    for u in graph.keys() {
1867        state.insert(u.clone(), 0);
1868    }
1869
1870    for u in graph.keys() {
1871        if state[u] != 0 {
1872            continue;
1873        }
1874        let mut stack: Vec<(String, usize)> = vec![(u.clone(), 0)];
1875        while let Some((node, idx)) = stack.pop() {
1876            if *state.get(&node).unwrap() == 2 {
1877                continue;
1878            }
1879            if *state.get(&node).unwrap() == 0 {
1880                state.insert(node.clone(), 1);
1881            }
1882            let neighbors = graph.get(&node).cloned().unwrap_or_default();
1883            if idx < neighbors.len() {
1884                let nbr = &neighbors[idx];
1885                stack.push((node, idx + 1));
1886                match *state.get(nbr).unwrap_or(&0) {
1887                    1 => return Ok(true),
1888                    0 => stack.push((nbr.clone(), 0)),
1889                    _ => {}
1890                }
1891            } else {
1892                state.insert(node, 2);
1893            }
1894        }
1895    }
1896    Ok(false)
1897}