|
1 | | -/* |
2 | | - A RangeMinimumQuery, is a data structure for answering range-minimum-queries of an array. |
3 | | - For a given array A[], of elements for which an ordering exists, we want to find the |
4 | | - minimum value A[x] of a subarray A[i..j], where i and j are the query parameters. |
5 | | -
|
6 | | - Precomputation complexity: O(n log(n)) |
7 | | - Query complexity: O(1) |
8 | | -
|
9 | | - Wikipedia: <https://en.wikipedia.org/wiki/Range_minimum_query> |
10 | | -*/ |
| 1 | +//! Range Minimum Query (RMQ) Implementation |
| 2 | +//! |
| 3 | +//! This module provides an efficient implementation of a Range Minimum Query data structure using a |
| 4 | +//! sparse table approach. It allows for quick retrieval of the minimum value within a specified subdata |
| 5 | +//! of a given data after an initial preprocessing phase. |
| 6 | +//! |
| 7 | +//! The RMQ is particularly useful in scenarios requiring multiple queries on static data, as it |
| 8 | +//! allows querying in constant time after an O(n log(n)) preprocessing time. |
| 9 | +//! |
| 10 | +//! References: [Wikipedia](https://en.wikipedia.org/wiki/Range_minimum_query) |
11 | 11 |
|
12 | 12 | use std::cmp::PartialOrd; |
13 | | -use std::fmt; |
14 | 13 |
|
15 | | -/// Custom error for invalid range |
16 | | -#[derive(Debug, PartialEq)] |
17 | | -pub struct RangeError; |
18 | | - |
19 | | -impl fmt::Display for RangeError { |
20 | | - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
21 | | - write!(f, "Invalid range") |
22 | | - } |
| 14 | +/// Custom error type for invalid range queries. |
| 15 | +#[derive(Debug, PartialEq, Eq)] |
| 16 | +pub enum RangeError { |
| 17 | + /// Indicates that the provided range is invalid (start index is not less than end index). |
| 18 | + InvalidRange, |
| 19 | + /// Indicates that one or more indices are out of bounds for the data. |
| 20 | + IndexOutOfBound, |
23 | 21 | } |
24 | 22 |
|
| 23 | +/// A data structure for efficiently answering range minimum queries on static data. |
25 | 24 | pub struct RangeMinimumQuery<T: PartialOrd + Copy> { |
26 | | - // the current version makes a copy of the input array, but this could be changed |
27 | | - // to references if needed (in that case, we dont need T to implement the Copy trait) |
28 | | - array: Vec<T>, |
| 25 | + /// The original input data on which range queries are performed. |
| 26 | + data: Vec<T>, |
| 27 | + /// The sparse table for storing preprocessed range minimum information. Each entry |
| 28 | + /// contains the index of the minimum element in the range starting at `j` and having a length of `2^i`. |
29 | 29 | sparse_table: Vec<Vec<usize>>, |
30 | 30 | } |
31 | 31 |
|
32 | 32 | impl<T: PartialOrd + Copy> RangeMinimumQuery<T> { |
| 33 | + /// Creates a new `RangeMinimumQuery` instance with the provided input data. |
| 34 | + /// |
| 35 | + /// # Arguments |
| 36 | + /// |
| 37 | + /// * `input` - A slice of elements of type `T` that implement `PartialOrd` and `Copy`. |
| 38 | + /// |
| 39 | + /// # Returns |
| 40 | + /// |
| 41 | + /// A `RangeMinimumQuery` instance that can be used to perform range minimum queries. |
33 | 42 | pub fn new(input: &[T]) -> RangeMinimumQuery<T> { |
34 | 43 | RangeMinimumQuery { |
35 | | - array: input.to_vec(), |
| 44 | + data: input.to_vec(), |
36 | 45 | sparse_table: build_sparse_table(input), |
37 | 46 | } |
38 | 47 | } |
39 | 48 |
|
| 49 | + /// Retrieves the minimum value in the specified range [start, end). |
| 50 | + /// |
| 51 | + /// # Arguments |
| 52 | + /// |
| 53 | + /// * `start` - The starting index of the range (inclusive). |
| 54 | + /// * `end` - The ending index of the range (exclusive). |
| 55 | + /// |
| 56 | + /// # Returns |
| 57 | + /// |
| 58 | + /// * `Ok(T)` - The minimum value found in the specified range. |
| 59 | + /// * `Err(RangeError)` - An error indicating the reason for failure, such as an invalid range |
| 60 | + /// or indices out of bounds. |
40 | 61 | pub fn get_range_min(&self, start: usize, end: usize) -> Result<T, RangeError> { |
41 | | - if start >= end || start >= self.array.len() || end > self.array.len() { |
42 | | - return Err(RangeError); |
| 62 | + // Validate range |
| 63 | + if start >= end { |
| 64 | + return Err(RangeError::InvalidRange); |
43 | 65 | } |
44 | | - let loglen = (end - start).ilog2() as usize; |
45 | | - let idx: usize = end - (1 << loglen); |
46 | | - let a = self.sparse_table[loglen][start]; |
47 | | - let b = self.sparse_table[loglen][idx]; |
48 | | - if self.array[a] < self.array[b] { |
49 | | - return Ok(self.array[a]); |
| 66 | + if start >= self.data.len() || end > self.data.len() { |
| 67 | + return Err(RangeError::IndexOutOfBound); |
| 68 | + } |
| 69 | + |
| 70 | + // Calculate the log length and the index for the sparse table |
| 71 | + let log_len = (end - start).ilog2() as usize; |
| 72 | + let idx: usize = end - (1 << log_len); |
| 73 | + |
| 74 | + // Retrieve the indices of the minimum values from the sparse table |
| 75 | + let min_idx_start = self.sparse_table[log_len][start]; |
| 76 | + let min_idx_end = self.sparse_table[log_len][idx]; |
| 77 | + |
| 78 | + // Compare the values at the retrieved indices and return the minimum |
| 79 | + if self.data[min_idx_start] < self.data[min_idx_end] { |
| 80 | + Ok(self.data[min_idx_start]) |
| 81 | + } else { |
| 82 | + Ok(self.data[min_idx_end]) |
50 | 83 | } |
51 | | - Ok(self.array[b]) |
52 | 84 | } |
53 | 85 | } |
54 | 86 |
|
55 | | -fn build_sparse_table<T: PartialOrd>(array: &[T]) -> Vec<Vec<usize>> { |
56 | | - let mut table: Vec<Vec<usize>> = vec![(0..array.len()).collect()]; |
57 | | - let len = array.len(); |
| 87 | +/// Builds a sparse table for the provided data to support range minimum queries. |
| 88 | +/// |
| 89 | +/// # Arguments |
| 90 | +/// |
| 91 | +/// * `data` - A slice of elements of type `T` that implement `PartialOrd`. |
| 92 | +/// |
| 93 | +/// # Returns |
| 94 | +/// |
| 95 | +/// A 2D vector representing the sparse table, where each entry contains the index of the minimum |
| 96 | +/// element in the range defined by the starting index and the power of two lengths. |
| 97 | +fn build_sparse_table<T: PartialOrd>(data: &[T]) -> Vec<Vec<usize>> { |
| 98 | + let mut sparse_table: Vec<Vec<usize>> = vec![(0..data.len()).collect()]; |
| 99 | + let len = data.len(); |
58 | 100 |
|
59 | | - for loglen in 1..=len.ilog2() { |
| 101 | + // Fill the sparse table |
| 102 | + for log_len in 1..=len.ilog2() { |
60 | 103 | let mut row = Vec::new(); |
61 | | - for i in 0..=len - (1 << loglen) { |
62 | | - let a = table[table.len() - 1][i]; |
63 | | - let b = table[table.len() - 1][i + (1 << (loglen - 1))]; |
64 | | - if array[a] < array[b] { |
65 | | - row.push(a); |
| 104 | + for idx in 0..=len - (1 << log_len) { |
| 105 | + let min_idx_start = sparse_table[sparse_table.len() - 1][idx]; |
| 106 | + let min_idx_end = sparse_table[sparse_table.len() - 1][idx + (1 << (log_len - 1))]; |
| 107 | + if data[min_idx_start] < data[min_idx_end] { |
| 108 | + row.push(min_idx_start); |
66 | 109 | } else { |
67 | | - row.push(b); |
| 110 | + row.push(min_idx_end); |
68 | 111 | } |
69 | 112 | } |
70 | | - table.push(row); |
| 113 | + sparse_table.push(row); |
71 | 114 | } |
72 | | - table |
| 115 | + |
| 116 | + sparse_table |
73 | 117 | } |
74 | 118 |
|
75 | 119 | #[cfg(test)] |
76 | 120 | mod tests { |
77 | | - use super::build_sparse_table; |
| 121 | + use super::*; |
| 122 | + |
78 | 123 | macro_rules! test_build_sparse_table { |
79 | 124 | ($($name:ident: $inputs:expr,)*) => { |
80 | | - $( |
81 | | - #[test] |
82 | | - fn $name() { |
83 | | - let (array, expected) = $inputs; |
84 | | - assert_eq!(build_sparse_table(&array), expected); |
85 | | - } |
86 | | - )* |
| 125 | + $( |
| 126 | + #[test] |
| 127 | + fn $name() { |
| 128 | + let (data, expected) = $inputs; |
| 129 | + assert_eq!(build_sparse_table(&data), expected); |
| 130 | + } |
| 131 | + )* |
87 | 132 | } |
88 | 133 | } |
| 134 | + |
89 | 135 | test_build_sparse_table! { |
90 | | - small: ([1, 6, 3], vec![vec![0, 1, 2], vec![0, 2]]), |
91 | | - tc_1: ([1, 3, 6, 123, 7, 235, 3, -4, 6, 2], vec![ |
92 | | - vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9], |
93 | | - vec![0, 1, 2, 4, 4, 6, 7, 7, 9], |
94 | | - vec![0, 1, 2, 6, 7, 7, 7], |
95 | | - vec![7, 7, 7] |
96 | | - ]), |
97 | | - tc_2: ([ |
98 | | - 20, 13, -13, 2, 3634, -2, 56, 3, 67, 8, 23, 0, -23, 1, 5, 85, 3, 24, 5, -10, 3, 4, 20, |
99 | | - ], vec![ |
100 | | - vec![ |
101 | | - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, |
102 | | - 22 |
103 | | - ], |
104 | | - vec![1, 2, 2, 3, 5, 5, 7, 7, 9, 9, 11, 12, 12, 13, 14, 16, 16, 18, 19, 19, 20, 21], |
105 | | - vec![2, 2, 2, 5, 5, 5, 7, 7, 11, 12, 12, 12, 12, 13, 16, 16, 19, 19, 19, 19], |
106 | | - vec![2, 2, 2, 5, 5, 12, 12, 12, 12, 12, 12, 12, 12, 19, 19, 19], |
107 | | - vec![12, 12, 12, 12, 12, 12, 12, 12] |
108 | | - ]), |
| 136 | + small: ( |
| 137 | + [1, 6, 3], |
| 138 | + vec![ |
| 139 | + vec![0, 1, 2], |
| 140 | + vec![0, 2] |
| 141 | + ] |
| 142 | + ), |
| 143 | + medium: ( |
| 144 | + [1, 3, 6, 123, 7, 235, 3, -4, 6, 2], |
| 145 | + vec![ |
| 146 | + vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9], |
| 147 | + vec![0, 1, 2, 4, 4, 6, 7, 7, 9], |
| 148 | + vec![0, 1, 2, 6, 7, 7, 7], |
| 149 | + vec![7, 7, 7] |
| 150 | + ] |
| 151 | + ), |
| 152 | + large: ( |
| 153 | + [20, 13, -13, 2, 3634, -2, 56, 3, 67, 8, 23, 0, -23, 1, 5, 85, 3, 24, 5, -10, 3, 4, 20], |
| 154 | + vec![ |
| 155 | + vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22], |
| 156 | + vec![1, 2, 2, 3, 5, 5, 7, 7, 9, 9, 11, 12, 12, 13, 14, 16, 16, 18, 19, 19, 20, 21], |
| 157 | + vec![2, 2, 2, 5, 5, 5, 7, 7, 11, 12, 12, 12, 12, 13, 16, 16, 19, 19, 19, 19], |
| 158 | + vec![2, 2, 2, 5, 5, 12, 12, 12, 12, 12, 12, 12, 12, 19, 19, 19], |
| 159 | + vec![12, 12, 12, 12, 12, 12, 12, 12] |
| 160 | + ] |
| 161 | + ), |
109 | 162 | } |
110 | 163 |
|
111 | 164 | #[test] |
112 | 165 | fn simple_query_tests() { |
113 | | - let v1 = vec![1, 3, 6, 123, 7, 235, 3, -4, 6, 2]; |
114 | | - let sparse_v1 = super::RangeMinimumQuery::new(&v1); |
115 | | - |
116 | | - assert_eq!(Ok(3), sparse_v1.get_range_min(1, 6)); |
117 | | - assert_eq!(Ok(-4), sparse_v1.get_range_min(0, 10)); |
118 | | - assert_eq!(Ok(6), sparse_v1.get_range_min(8, 9)); |
119 | | - assert!(sparse_v1.get_range_min(4, 3).is_err()); |
120 | | - assert!(sparse_v1.get_range_min(0, 1000).is_err()); |
121 | | - assert!(sparse_v1.get_range_min(1000, 1001).is_err()); |
| 166 | + let rmq = RangeMinimumQuery::new(&[1, 3, 6, 123, 7, 235, 3, -4, 6, 2]); |
| 167 | + |
| 168 | + assert_eq!(rmq.get_range_min(1, 6), Ok(3)); |
| 169 | + assert_eq!(rmq.get_range_min(0, 10), Ok(-4)); |
| 170 | + assert_eq!(rmq.get_range_min(8, 9), Ok(6)); |
| 171 | + assert_eq!(rmq.get_range_min(4, 3), Err(RangeError::InvalidRange)); |
| 172 | + assert_eq!(rmq.get_range_min(0, 1000), Err(RangeError::IndexOutOfBound)); |
| 173 | + assert_eq!( |
| 174 | + rmq.get_range_min(1000, 1001), |
| 175 | + Err(RangeError::IndexOutOfBound) |
| 176 | + ); |
122 | 177 | } |
123 | 178 |
|
124 | 179 | #[test] |
125 | 180 | fn float_query_tests() { |
126 | | - let sparse_v1 = super::RangeMinimumQuery::new(&[0.4, -2.3, 0.0, 234.22, 12.2, -3.0]); |
| 181 | + let rmq = RangeMinimumQuery::new(&[0.4, -2.3, 0.0, 234.22, 12.2, -3.0]); |
127 | 182 |
|
128 | | - assert_eq!(Ok(-3.0), sparse_v1.get_range_min(0, 6)); |
129 | | - assert_eq!(Ok(-2.3), sparse_v1.get_range_min(0, 4)); |
130 | | - assert_eq!(Ok(12.2), sparse_v1.get_range_min(3, 5)); |
131 | | - assert_eq!(Ok(0.0), sparse_v1.get_range_min(2, 3)); |
| 183 | + assert_eq!(rmq.get_range_min(0, 6), Ok(-3.0)); |
| 184 | + assert_eq!(rmq.get_range_min(0, 4), Ok(-2.3)); |
| 185 | + assert_eq!(rmq.get_range_min(3, 5), Ok(12.2)); |
| 186 | + assert_eq!(rmq.get_range_min(2, 3), Ok(0.0)); |
| 187 | + assert_eq!(rmq.get_range_min(4, 3), Err(RangeError::InvalidRange)); |
| 188 | + assert_eq!(rmq.get_range_min(0, 1000), Err(RangeError::IndexOutOfBound)); |
| 189 | + assert_eq!( |
| 190 | + rmq.get_range_min(1000, 1001), |
| 191 | + Err(RangeError::IndexOutOfBound) |
| 192 | + ); |
132 | 193 | } |
133 | 194 | } |
0 commit comments