/rust/registry/src/index.crates.io-6f17d22bba15001f/smawk-0.3.2/src/lib.rs
Line | Count | Source (jump to first uncovered line) |
1 | | //! This crate implements various functions that help speed up dynamic |
2 | | //! programming, most importantly the SMAWK algorithm for finding row |
3 | | //! or column minima in a totally monotone matrix with *m* rows and |
4 | | //! *n* columns in time O(*m* + *n*). This is much better than the |
5 | | //! brute force solution which would take O(*mn*). When *m* and *n* |
6 | | //! are of the same order, this turns a quadratic function into a |
7 | | //! linear function. |
8 | | //! |
9 | | //! # Examples |
10 | | //! |
11 | | //! Computing the column minima of an *m* ✕ *n* Monge matrix can be |
12 | | //! done efficiently with `smawk::column_minima`: |
13 | | //! |
14 | | //! ``` |
15 | | //! use smawk::Matrix; |
16 | | //! |
17 | | //! let matrix = vec![ |
18 | | //! vec![3, 2, 4, 5, 6], |
19 | | //! vec![2, 1, 3, 3, 4], |
20 | | //! vec![2, 1, 3, 3, 4], |
21 | | //! vec![3, 2, 4, 3, 4], |
22 | | //! vec![4, 3, 2, 1, 1], |
23 | | //! ]; |
24 | | //! let minima = vec![1, 1, 4, 4, 4]; |
25 | | //! assert_eq!(smawk::column_minima(&matrix), minima); |
26 | | //! ``` |
27 | | //! |
28 | | //! The `minima` vector gives the index of the minimum value per |
29 | | //! column, so `minima[0] == 1` since the minimum value in the first |
30 | | //! column is 2 (row 1). Note that the smallest row index is returned. |
31 | | //! |
32 | | //! # Definitions |
33 | | //! |
34 | | //! Some of the functions in this crate only work on matrices that are |
35 | | //! *totally monotone*, which we will define below. |
36 | | //! |
37 | | //! ## Monotone Matrices |
38 | | //! |
39 | | //! We start with a helper definition. Given an *m* ✕ *n* matrix `M`, |
40 | | //! we say that `M` is *monotone* when the minimum value of row `i` is |
41 | | //! found to the left of the minimum value in row `i'` where `i < i'`. |
42 | | //! |
43 | | //! More formally, if we let `rm(i)` denote the column index of the |
44 | | //! left-most minimum value in row `i`, then we have |
45 | | //! |
46 | | //! ```text |
47 | | //! rm(0) ≤ rm(1) ≤ ... ≤ rm(m - 1) |
48 | | //! ``` |
49 | | //! |
50 | | //! This means that as you go down the rows from top to bottom, the |
51 | | //! row-minima proceed from left to right. |
52 | | //! |
53 | | //! The algorithms in this crate deal with finding such row- and |
54 | | //! column-minima. |
55 | | //! |
56 | | //! ## Totally Monotone Matrices |
57 | | //! |
58 | | //! We say that a matrix `M` is *totally monotone* when every |
59 | | //! sub-matrix is monotone. A sub-matrix is formed by the intersection |
60 | | //! of any two rows `i < i'` and any two columns `j < j'`. |
61 | | //! |
62 | | //! This is often expressed as via this equivalent condition: |
63 | | //! |
64 | | //! ```text |
65 | | //! M[i, j] > M[i, j'] => M[i', j] > M[i', j'] |
66 | | //! ``` |
67 | | //! |
68 | | //! for all `i < i'` and `j < j'`. |
69 | | //! |
70 | | //! ## Monge Property for Matrices |
71 | | //! |
72 | | //! A matrix `M` is said to fulfill the *Monge property* if |
73 | | //! |
74 | | //! ```text |
75 | | //! M[i, j] + M[i', j'] ≤ M[i, j'] + M[i', j] |
76 | | //! ``` |
77 | | //! |
78 | | //! for all `i < i'` and `j < j'`. This says that given any rectangle |
79 | | //! in the matrix, the sum of the top-left and bottom-right corners is |
80 | | //! less than or equal to the sum of the bottom-left and upper-right |
81 | | //! corners. |
82 | | //! |
83 | | //! All Monge matrices are totally monotone, so it is enough to |
84 | | //! establish that the Monge property holds in order to use a matrix |
85 | | //! with the functions in this crate. If your program is dealing with |
86 | | //! unknown inputs, it can use [`monge::is_monge`] to verify that a |
87 | | //! matrix is a Monge matrix. |
88 | | |
89 | | #![doc(html_root_url = "https://docs.rs/smawk/0.3.2")] |
90 | | // The s! macro from ndarray uses unsafe internally, so we can only |
91 | | // forbid unsafe code when building with the default features. |
92 | | #![cfg_attr(not(feature = "ndarray"), forbid(unsafe_code))] |
93 | | |
94 | | #[cfg(feature = "ndarray")] |
95 | | pub mod brute_force; |
96 | | pub mod monge; |
97 | | #[cfg(feature = "ndarray")] |
98 | | pub mod recursive; |
99 | | |
100 | | /// Minimal matrix trait for two-dimensional arrays. |
101 | | /// |
102 | | /// This provides the functionality needed to represent a read-only |
103 | | /// numeric matrix. You can query the size of the matrix and access |
104 | | /// elements. Modeled after [`ndarray::Array2`] from the [ndarray |
105 | | /// crate](https://crates.io/crates/ndarray). |
106 | | /// |
107 | | /// Enable the `ndarray` Cargo feature if you want to use it with |
108 | | /// `ndarray::Array2`. |
109 | | pub trait Matrix<T: Copy> { |
110 | | /// Return the number of rows. |
111 | | fn nrows(&self) -> usize; |
112 | | /// Return the number of columns. |
113 | | fn ncols(&self) -> usize; |
114 | | /// Return a matrix element. |
115 | | fn index(&self, row: usize, column: usize) -> T; |
116 | | } |
117 | | |
118 | | /// Simple and inefficient matrix representation used for doctest |
119 | | /// examples and simple unit tests. |
120 | | /// |
121 | | /// You should prefer implementing it yourself, or you can enable the |
122 | | /// `ndarray` Cargo feature and use the provided implementation for |
123 | | /// [`ndarray::Array2`]. |
124 | | impl<T: Copy> Matrix<T> for Vec<Vec<T>> { |
125 | | fn nrows(&self) -> usize { |
126 | | self.len() |
127 | | } |
128 | | fn ncols(&self) -> usize { |
129 | | self[0].len() |
130 | | } |
131 | | fn index(&self, row: usize, column: usize) -> T { |
132 | | self[row][column] |
133 | | } |
134 | | } |
135 | | |
136 | | /// Adapting [`ndarray::Array2`] to the `Matrix` trait. |
137 | | /// |
138 | | /// **Note: this implementation is only available if you enable the |
139 | | /// `ndarray` Cargo feature.** |
140 | | #[cfg(feature = "ndarray")] |
141 | | impl<T: Copy> Matrix<T> for ndarray::Array2<T> { |
142 | | #[inline] |
143 | | fn nrows(&self) -> usize { |
144 | | self.nrows() |
145 | | } |
146 | | #[inline] |
147 | | fn ncols(&self) -> usize { |
148 | | self.ncols() |
149 | | } |
150 | | #[inline] |
151 | | fn index(&self, row: usize, column: usize) -> T { |
152 | | self[[row, column]] |
153 | | } |
154 | | } |
155 | | |
156 | | /// Compute row minima in O(*m* + *n*) time. |
157 | | /// |
158 | | /// This implements the [SMAWK algorithm] for efficiently finding row |
159 | | /// minima in a totally monotone matrix. |
160 | | /// |
161 | | /// The SMAWK algorithm is from Agarwal, Klawe, Moran, Shor, and |
162 | | /// Wilbur, *Geometric applications of a matrix searching algorithm*, |
163 | | /// Algorithmica 2, pp. 195-208 (1987) and the code here is a |
164 | | /// translation [David Eppstein's Python code][pads]. |
165 | | /// |
166 | | /// Running time on an *m* ✕ *n* matrix: O(*m* + *n*). |
167 | | /// |
168 | | /// # Examples |
169 | | /// |
170 | | /// ``` |
171 | | /// use smawk::Matrix; |
172 | | /// let matrix = vec![vec![4, 2, 4, 3], |
173 | | /// vec![5, 3, 5, 3], |
174 | | /// vec![5, 3, 3, 1]]; |
175 | | /// assert_eq!(smawk::row_minima(&matrix), |
176 | | /// vec![1, 1, 3]); |
177 | | /// ``` |
178 | | /// |
179 | | /// # Panics |
180 | | /// |
181 | | /// It is an error to call this on a matrix with zero columns. |
182 | | /// |
183 | | /// [pads]: https://github.com/jfinkels/PADS/blob/master/pads/smawk.py |
184 | | /// [SMAWK algorithm]: https://en.wikipedia.org/wiki/SMAWK_algorithm |
185 | | pub fn row_minima<T: PartialOrd + Copy, M: Matrix<T>>(matrix: &M) -> Vec<usize> { |
186 | | // Benchmarking shows that SMAWK performs roughly the same on row- |
187 | | // and column-major matrices. |
188 | | let mut minima = vec![0; matrix.nrows()]; |
189 | | smawk_inner( |
190 | | &|j, i| matrix.index(i, j), |
191 | | &(0..matrix.ncols()).collect::<Vec<_>>(), |
192 | | &(0..matrix.nrows()).collect::<Vec<_>>(), |
193 | | &mut minima, |
194 | | ); |
195 | | minima |
196 | | } |
197 | | |
198 | | #[deprecated(since = "0.3.2", note = "Please use `row_minima` instead.")] |
199 | | pub fn smawk_row_minima<T: PartialOrd + Copy, M: Matrix<T>>(matrix: &M) -> Vec<usize> { |
200 | | row_minima(matrix) |
201 | | } |
202 | | |
203 | | /// Compute column minima in O(*m* + *n*) time. |
204 | | /// |
205 | | /// This implements the [SMAWK algorithm] for efficiently finding |
206 | | /// column minima in a totally monotone matrix. |
207 | | /// |
208 | | /// The SMAWK algorithm is from Agarwal, Klawe, Moran, Shor, and |
209 | | /// Wilbur, *Geometric applications of a matrix searching algorithm*, |
210 | | /// Algorithmica 2, pp. 195-208 (1987) and the code here is a |
211 | | /// translation [David Eppstein's Python code][pads]. |
212 | | /// |
213 | | /// Running time on an *m* ✕ *n* matrix: O(*m* + *n*). |
214 | | /// |
215 | | /// # Examples |
216 | | /// |
217 | | /// ``` |
218 | | /// use smawk::Matrix; |
219 | | /// let matrix = vec![vec![4, 2, 4, 3], |
220 | | /// vec![5, 3, 5, 3], |
221 | | /// vec![5, 3, 3, 1]]; |
222 | | /// assert_eq!(smawk::column_minima(&matrix), |
223 | | /// vec![0, 0, 2, 2]); |
224 | | /// ``` |
225 | | /// |
226 | | /// # Panics |
227 | | /// |
228 | | /// It is an error to call this on a matrix with zero rows. |
229 | | /// |
230 | | /// [SMAWK algorithm]: https://en.wikipedia.org/wiki/SMAWK_algorithm |
231 | | /// [pads]: https://github.com/jfinkels/PADS/blob/master/pads/smawk.py |
232 | | pub fn column_minima<T: PartialOrd + Copy, M: Matrix<T>>(matrix: &M) -> Vec<usize> { |
233 | | let mut minima = vec![0; matrix.ncols()]; |
234 | | smawk_inner( |
235 | | &|i, j| matrix.index(i, j), |
236 | | &(0..matrix.nrows()).collect::<Vec<_>>(), |
237 | | &(0..matrix.ncols()).collect::<Vec<_>>(), |
238 | | &mut minima, |
239 | | ); |
240 | | minima |
241 | | } |
242 | | |
243 | | #[deprecated(since = "0.3.2", note = "Please use `column_minima` instead.")] |
244 | | pub fn smawk_column_minima<T: PartialOrd + Copy, M: Matrix<T>>(matrix: &M) -> Vec<usize> { |
245 | | column_minima(matrix) |
246 | | } |
247 | | |
248 | | /// Compute column minima in the given area of the matrix. The |
249 | | /// `minima` slice is updated inplace. |
250 | 21.7M | fn smawk_inner<T: PartialOrd + Copy, M: Fn(usize, usize) -> T>( |
251 | 21.7M | matrix: &M, |
252 | 21.7M | rows: &[usize], |
253 | 21.7M | cols: &[usize], |
254 | 21.7M | minima: &mut [usize], |
255 | 21.7M | ) { |
256 | 21.7M | if cols.is_empty() { |
257 | 7.05M | return; |
258 | 14.6M | } |
259 | 14.6M | |
260 | 14.6M | let mut stack = Vec::with_capacity(cols.len()); |
261 | 84.7M | for r in rows { |
262 | | // TODO: use stack.last() instead of stack.is_empty() etc |
263 | 84.0M | while !stack.is_empty() |
264 | 62.0M | && matrix(stack[stack.len() - 1], cols[stack.len() - 1]) |
265 | 62.0M | > matrix(*r, cols[stack.len() - 1]) |
266 | 13.9M | { |
267 | 13.9M | stack.pop(); |
268 | 13.9M | } |
269 | 70.1M | if stack.len() != cols.len() { |
270 | 56.7M | stack.push(*r); |
271 | 56.7M | } |
272 | | } |
273 | 14.6M | let rows = &stack; |
274 | 14.6M | |
275 | 14.6M | let mut odd_cols = Vec::with_capacity(1 + cols.len() / 2); |
276 | 53.0M | for (idx, c) in cols.iter().enumerate() { |
277 | 53.0M | if idx % 2 == 1 { |
278 | 22.9M | odd_cols.push(*c); |
279 | 30.0M | } |
280 | | } |
281 | | |
282 | 14.6M | smawk_inner(matrix, rows, &odd_cols, minima); |
283 | 14.6M | |
284 | 14.6M | let mut r = 0; |
285 | 53.0M | for (c, &col) in cols.iter().enumerate().filter(|(c, _)| c % 2 == 0) { smawk::smawk_inner::<f64, smawk::online_column_minima<f64, textwrap::wrap_algorithms::optimal_fit::wrap_optimal_fit<textwrap::core::Word>::{closure#0}>::{closure#0}>::{closure#0} Line | Count | Source | 285 | 44.7M | for (c, &col) in cols.iter().enumerate().filter(|(c, _)| c % 2 == 0) { |
smawk::smawk_inner::<f64, smawk::online_column_minima<f64, textwrap::wrap_algorithms::optimal_fit::wrap_optimal_fit<wrap_optimal_fit_usize::Word>::{closure#0}>::{closure#0}>::{closure#0} Line | Count | Source | 285 | 4.10M | for (c, &col) in cols.iter().enumerate().filter(|(c, _)| c % 2 == 0) { |
smawk::smawk_inner::<f64, smawk::online_column_minima<f64, textwrap::wrap_algorithms::optimal_fit::wrap_optimal_fit<wrap_optimal_fit::Word>::{closure#0}>::{closure#0}>::{closure#0} Line | Count | Source | 285 | 4.22M | for (c, &col) in cols.iter().enumerate().filter(|(c, _)| c % 2 == 0) { |
|
286 | 30.0M | let mut row = rows[r]; |
287 | 30.0M | let last_row = if c == cols.len() - 1 { |
288 | 7.06M | rows[rows.len() - 1] |
289 | | } else { |
290 | 22.9M | minima[cols[c + 1]] |
291 | | }; |
292 | 30.0M | let mut pair = (matrix(row, col), row); |
293 | 37.2M | while row != last_row { |
294 | 7.21M | r += 1; |
295 | 7.21M | row = rows[r]; |
296 | 7.21M | if (matrix(row, col), row) < pair { |
297 | 1.20M | pair = (matrix(row, col), row); |
298 | 6.00M | } |
299 | | } |
300 | 30.0M | minima[col] = pair.1; |
301 | | } |
302 | 21.7M | } smawk::smawk_inner::<f64, smawk::online_column_minima<f64, textwrap::wrap_algorithms::optimal_fit::wrap_optimal_fit<textwrap::core::Word>::{closure#0}>::{closure#0}> Line | Count | Source | 250 | 19.4M | fn smawk_inner<T: PartialOrd + Copy, M: Fn(usize, usize) -> T>( | 251 | 19.4M | matrix: &M, | 252 | 19.4M | rows: &[usize], | 253 | 19.4M | cols: &[usize], | 254 | 19.4M | minima: &mut [usize], | 255 | 19.4M | ) { | 256 | 19.4M | if cols.is_empty() { | 257 | 6.38M | return; | 258 | 13.1M | } | 259 | 13.1M | | 260 | 13.1M | let mut stack = Vec::with_capacity(cols.len()); | 261 | 71.1M | for r in rows { | 262 | | // TODO: use stack.last() instead of stack.is_empty() etc | 263 | 70.9M | while !stack.is_empty() | 264 | 51.2M | && matrix(stack[stack.len() - 1], cols[stack.len() - 1]) | 265 | 51.2M | > matrix(*r, cols[stack.len() - 1]) | 266 | 12.9M | { | 267 | 12.9M | stack.pop(); | 268 | 12.9M | } | 269 | 57.9M | if stack.len() != cols.len() { | 270 | 48.3M | stack.push(*r); | 271 | 48.3M | } | 272 | | } | 273 | 13.1M | let rows = &stack; | 274 | 13.1M | | 275 | 13.1M | let mut odd_cols = Vec::with_capacity(1 + cols.len() / 2); | 276 | 44.7M | for (idx, c) in cols.iter().enumerate() { | 277 | 44.7M | if idx % 2 == 1 { | 278 | 19.1M | odd_cols.push(*c); | 279 | 25.5M | } | 280 | | } | 281 | | | 282 | 13.1M | smawk_inner(matrix, rows, &odd_cols, minima); | 283 | 13.1M | | 284 | 13.1M | let mut r = 0; | 285 | 25.5M | for (c, &col) in cols.iter().enumerate().filter(|(c, _)| c % 2 == 0) { | 286 | 25.5M | let mut row = rows[r]; | 287 | 25.5M | let last_row = if c == cols.len() - 1 { | 288 | 6.39M | rows[rows.len() - 1] | 289 | | } else { | 290 | 19.1M | minima[cols[c + 1]] | 291 | | }; | 292 | 25.5M | let mut pair = (matrix(row, col), row); | 293 | 31.9M | while row != last_row { | 294 | 6.35M | r += 1; | 295 | 6.35M | row = rows[r]; | 296 | 6.35M | if (matrix(row, col), row) < pair { | 297 | 1.17M | pair = (matrix(row, col), row); | 298 | 5.17M | } | 299 | | } | 300 | 25.5M | minima[col] = pair.1; | 301 | | } | 302 | 19.4M | } |
smawk::smawk_inner::<f64, smawk::online_column_minima<f64, textwrap::wrap_algorithms::optimal_fit::wrap_optimal_fit<wrap_optimal_fit_usize::Word>::{closure#0}>::{closure#0}> Line | Count | Source | 250 | 1.45M | fn smawk_inner<T: PartialOrd + Copy, M: Fn(usize, usize) -> T>( | 251 | 1.45M | matrix: &M, | 252 | 1.45M | rows: &[usize], | 253 | 1.45M | cols: &[usize], | 254 | 1.45M | minima: &mut [usize], | 255 | 1.45M | ) { | 256 | 1.45M | if cols.is_empty() { | 257 | 435k | return; | 258 | 1.01M | } | 259 | 1.01M | | 260 | 1.01M | let mut stack = Vec::with_capacity(cols.len()); | 261 | 6.82M | for r in rows { | 262 | | // TODO: use stack.last() instead of stack.is_empty() etc | 263 | 6.46M | while !stack.is_empty() | 264 | 4.89M | && matrix(stack[stack.len() - 1], cols[stack.len() - 1]) | 265 | 4.89M | > matrix(*r, cols[stack.len() - 1]) | 266 | 647k | { | 267 | 647k | stack.pop(); | 268 | 647k | } | 269 | 5.81M | if stack.len() != cols.len() { | 270 | 4.14M | stack.push(*r); | 271 | 4.14M | } | 272 | | } | 273 | 1.01M | let rows = &stack; | 274 | 1.01M | | 275 | 1.01M | let mut odd_cols = Vec::with_capacity(1 + cols.len() / 2); | 276 | 4.10M | for (idx, c) in cols.iter().enumerate() { | 277 | 4.10M | if idx % 2 == 1 { | 278 | 1.83M | odd_cols.push(*c); | 279 | 2.27M | } | 280 | | } | 281 | | | 282 | 1.01M | smawk_inner(matrix, rows, &odd_cols, minima); | 283 | 1.01M | | 284 | 1.01M | let mut r = 0; | 285 | 2.27M | for (c, &col) in cols.iter().enumerate().filter(|(c, _)| c % 2 == 0) { | 286 | 2.27M | let mut row = rows[r]; | 287 | 2.27M | let last_row = if c == cols.len() - 1 { | 288 | 436k | rows[rows.len() - 1] | 289 | | } else { | 290 | 1.83M | minima[cols[c + 1]] | 291 | | }; | 292 | 2.27M | let mut pair = (matrix(row, col), row); | 293 | 2.64M | while row != last_row { | 294 | 373k | r += 1; | 295 | 373k | row = rows[r]; | 296 | 373k | if (matrix(row, col), row) < pair { | 297 | 11.9k | pair = (matrix(row, col), row); | 298 | 361k | } | 299 | | } | 300 | 2.27M | minima[col] = pair.1; | 301 | | } | 302 | 1.45M | } |
smawk::smawk_inner::<f64, smawk::online_column_minima<f64, textwrap::wrap_algorithms::optimal_fit::wrap_optimal_fit<wrap_optimal_fit::Word>::{closure#0}>::{closure#0}> Line | Count | Source | 250 | 774k | fn smawk_inner<T: PartialOrd + Copy, M: Fn(usize, usize) -> T>( | 251 | 774k | matrix: &M, | 252 | 774k | rows: &[usize], | 253 | 774k | cols: &[usize], | 254 | 774k | minima: &mut [usize], | 255 | 774k | ) { | 256 | 774k | if cols.is_empty() { | 257 | 229k | return; | 258 | 545k | } | 259 | 545k | | 260 | 545k | let mut stack = Vec::with_capacity(cols.len()); | 261 | 6.84M | for r in rows { | 262 | | // TODO: use stack.last() instead of stack.is_empty() etc | 263 | 6.64M | while !stack.is_empty() | 264 | 5.89M | && matrix(stack[stack.len() - 1], cols[stack.len() - 1]) | 265 | 5.89M | > matrix(*r, cols[stack.len() - 1]) | 266 | 337k | { | 267 | 337k | stack.pop(); | 268 | 337k | } | 269 | 6.30M | if stack.len() != cols.len() { | 270 | 4.25M | stack.push(*r); | 271 | 4.25M | } | 272 | | } | 273 | 545k | let rows = &stack; | 274 | 545k | | 275 | 545k | let mut odd_cols = Vec::with_capacity(1 + cols.len() / 2); | 276 | 4.22M | for (idx, c) in cols.iter().enumerate() { | 277 | 4.22M | if idx % 2 == 1 { | 278 | 1.99M | odd_cols.push(*c); | 279 | 2.22M | } | 280 | | } | 281 | | | 282 | 545k | smawk_inner(matrix, rows, &odd_cols, minima); | 283 | 545k | | 284 | 545k | let mut r = 0; | 285 | 2.22M | for (c, &col) in cols.iter().enumerate().filter(|(c, _)| c % 2 == 0) { | 286 | 2.22M | let mut row = rows[r]; | 287 | 2.22M | let last_row = if c == cols.len() - 1 { | 288 | 230k | rows[rows.len() - 1] | 289 | | } else { | 290 | 1.99M | minima[cols[c + 1]] | 291 | | }; | 292 | 2.22M | let mut pair = (matrix(row, col), row); | 293 | 2.71M | while row != last_row { | 294 | 481k | r += 1; | 295 | 481k | row = rows[r]; | 296 | 481k | if (matrix(row, col), row) < pair { | 297 | 16.0k | pair = (matrix(row, col), row); | 298 | 465k | } | 299 | | } | 300 | 2.22M | minima[col] = pair.1; | 301 | | } | 302 | 774k | } |
|
303 | | |
304 | | /// Compute upper-right column minima in O(*m* + *n*) time. |
305 | | /// |
306 | | /// The input matrix must be totally monotone. |
307 | | /// |
308 | | /// The function returns a vector of `(usize, T)`. The `usize` in the |
309 | | /// tuple at index `j` tells you the row of the minimum value in |
310 | | /// column `j` and the `T` value is minimum value itself. |
311 | | /// |
312 | | /// The algorithm only considers values above the main diagonal, which |
313 | | /// means that it computes values `v(j)` where: |
314 | | /// |
315 | | /// ```text |
316 | | /// v(0) = initial |
317 | | /// v(j) = min { M[i, j] | i < j } for j > 0 |
318 | | /// ``` |
319 | | /// |
320 | | /// If we let `r(j)` denote the row index of the minimum value in |
321 | | /// column `j`, the tuples in the result vector become `(r(j), M[r(j), |
322 | | /// j])`. |
323 | | /// |
324 | | /// The algorithm is an *online* algorithm, in the sense that `matrix` |
325 | | /// function can refer back to previously computed column minima when |
326 | | /// determining an entry in the matrix. The guarantee is that we only |
327 | | /// call `matrix(i, j)` after having computed `v(i)`. This is |
328 | | /// reflected in the `&[(usize, T)]` argument to `matrix`, which grows |
329 | | /// as more and more values are computed. |
330 | 451k | pub fn online_column_minima<T: Copy + PartialOrd, M: Fn(&[(usize, T)], usize, usize) -> T>( |
331 | 451k | initial: T, |
332 | 451k | size: usize, |
333 | 451k | matrix: M, |
334 | 451k | ) -> Vec<(usize, T)> { |
335 | 451k | let mut result = vec![(0, initial)]; |
336 | 451k | |
337 | 451k | // State used by the algorithm. |
338 | 451k | let mut finished = 0; |
339 | 451k | let mut base = 0; |
340 | 451k | let mut tentative = 0; |
341 | | |
342 | | // Shorthand for evaluating the matrix. We need a macro here since |
343 | | // we don't want to borrow the result vector. |
344 | | macro_rules! m { |
345 | | ($i:expr, $j:expr) => {{ |
346 | | assert!($i < $j, "(i, j) not above diagonal: ({}, {})", $i, $j); |
347 | | assert!( |
348 | | $i < size && $j < size, |
349 | | "(i, j) out of bounds: ({}, {}), size: {}", |
350 | | $i, |
351 | | $j, |
352 | | size |
353 | | ); |
354 | | matrix(&result[..finished + 1], $i, $j) |
355 | | }}; |
356 | | } |
357 | | |
358 | | // Keep going until we have finished all size columns. Since the |
359 | | // columns are zero-indexed, we're done when finished == size - 1. |
360 | 26.9M | while finished < size - 1 { |
361 | | // First case: we have already advanced past the previous |
362 | | // tentative value. We make a new tentative value by applying |
363 | | // smawk_inner to the largest square submatrix that fits under |
364 | | // the base. |
365 | 26.5M | let i = finished + 1; |
366 | 26.5M | if i > tentative { |
367 | 7.05M | let rows = (base..finished + 1).collect::<Vec<_>>(); |
368 | 7.05M | tentative = std::cmp::min(finished + rows.len(), size - 1); |
369 | 7.05M | let cols = (finished + 1..tentative + 1).collect::<Vec<_>>(); |
370 | 7.05M | let mut minima = vec![0; tentative + 1]; |
371 | 162M | smawk_inner(&|i, j| m![i, j], &rows, &cols, &mut minima); smawk::online_column_minima::<f64, textwrap::wrap_algorithms::optimal_fit::wrap_optimal_fit<textwrap::core::Word>::{closure#0}>::{closure#0} Line | Count | Source | 371 | 135M | smawk_inner(&|i, j| m![i, j], &rows, &cols, &mut minima); |
smawk::online_column_minima::<f64, textwrap::wrap_algorithms::optimal_fit::wrap_optimal_fit<wrap_optimal_fit_usize::Word>::{closure#0}>::{closure#0} Line | Count | Source | 371 | 12.4M | smawk_inner(&|i, j| m![i, j], &rows, &cols, &mut minima); |
smawk::online_column_minima::<f64, textwrap::wrap_algorithms::optimal_fit::wrap_optimal_fit<wrap_optimal_fit::Word>::{closure#0}>::{closure#0} Line | Count | Source | 371 | 14.5M | smawk_inner(&|i, j| m![i, j], &rows, &cols, &mut minima); |
|
372 | 37.1M | for col in cols { |
373 | 30.0M | let row = minima[col]; |
374 | 30.0M | let v = m![row, col]; |
375 | 30.0M | if col >= result.len() { |
376 | 26.5M | result.push((row, v)); |
377 | 26.5M | } else if v < result[col].1 { |
378 | 1.76M | result[col] = (row, v); |
379 | 1.76M | } |
380 | | } |
381 | 7.05M | finished = i; |
382 | 7.05M | continue; |
383 | 19.4M | } |
384 | | |
385 | | // Second case: the new column minimum is on the diagonal. All |
386 | | // subsequent ones will be at least as low, so we can clear |
387 | | // out all our work from higher rows. As in the fourth case, |
388 | | // the loss of tentative is amortized against the increase in |
389 | | // base. |
390 | 19.4M | let diag = m![i - 1, i]; |
391 | 19.4M | if diag < result[i].1 { |
392 | 6.57M | result[i] = (i - 1, diag); |
393 | 6.57M | base = i - 1; |
394 | 6.57M | tentative = i; |
395 | 6.57M | finished = i; |
396 | 6.57M | continue; |
397 | 12.8M | } |
398 | 12.8M | |
399 | 12.8M | // Third case: row i-1 does not supply a column minimum in any |
400 | 12.8M | // column up to tentative. We simply advance finished while |
401 | 12.8M | // maintaining the invariant. |
402 | 12.8M | if m![i - 1, tentative] >= result[tentative].1 { |
403 | 12.8M | finished = i; |
404 | 12.8M | continue; |
405 | 61.4k | } |
406 | 61.4k | |
407 | 61.4k | // Fourth and final case: a new column minimum at tentative. |
408 | 61.4k | // This allows us to make progress by incorporating rows prior |
409 | 61.4k | // to finished into the base. The base invariant holds because |
410 | 61.4k | // these rows cannot supply any later column minima. The work |
411 | 61.4k | // done when we last advanced tentative (and undone by this |
412 | 61.4k | // step) can be amortized against the increase in base. |
413 | 61.4k | base = i - 1; |
414 | 61.4k | tentative = i; |
415 | 61.4k | finished = i; |
416 | | } |
417 | | |
418 | 451k | result |
419 | 451k | } smawk::online_column_minima::<f64, textwrap::wrap_algorithms::optimal_fit::wrap_optimal_fit<textwrap::core::Word>::{closure#0}> Line | Count | Source | 330 | 449k | pub fn online_column_minima<T: Copy + PartialOrd, M: Fn(&[(usize, T)], usize, usize) -> T>( | 331 | 449k | initial: T, | 332 | 449k | size: usize, | 333 | 449k | matrix: M, | 334 | 449k | ) -> Vec<(usize, T)> { | 335 | 449k | let mut result = vec![(0, initial)]; | 336 | 449k | | 337 | 449k | // State used by the algorithm. | 338 | 449k | let mut finished = 0; | 339 | 449k | let mut base = 0; | 340 | 449k | let mut tentative = 0; | 341 | | | 342 | | // Shorthand for evaluating the matrix. We need a macro here since | 343 | | // we don't want to borrow the result vector. | 344 | | macro_rules! m { | 345 | | ($i:expr, $j:expr) => {{ | 346 | | assert!($i < $j, "(i, j) not above diagonal: ({}, {})", $i, $j); | 347 | | assert!( | 348 | | $i < size && $j < size, | 349 | | "(i, j) out of bounds: ({}, {}), size: {}", | 350 | | $i, | 351 | | $j, | 352 | | size | 353 | | ); | 354 | | matrix(&result[..finished + 1], $i, $j) | 355 | | }}; | 356 | | } | 357 | | | 358 | | // Keep going until we have finished all size columns. Since the | 359 | | // columns are zero-indexed, we're done when finished == size - 1. | 360 | 23.1M | while finished < size - 1 { | 361 | | // First case: we have already advanced past the previous | 362 | | // tentative value. We make a new tentative value by applying | 363 | | // smawk_inner to the largest square submatrix that fits under | 364 | | // the base. | 365 | 22.6M | let i = finished + 1; | 366 | 22.6M | if i > tentative { | 367 | 6.38M | let rows = (base..finished + 1).collect::<Vec<_>>(); | 368 | 6.38M | tentative = std::cmp::min(finished + rows.len(), size - 1); | 369 | 6.38M | let cols = (finished + 1..tentative + 1).collect::<Vec<_>>(); | 370 | 6.38M | let mut minima = vec![0; tentative + 1]; | 371 | 6.38M | smawk_inner(&|i, j| m![i, j], &rows, &cols, &mut minima); | 372 | 31.9M | for col in cols { | 373 | 25.5M | let row = minima[col]; | 374 | 25.5M | let v = m![row, col]; | 375 | 25.5M | if col >= result.len() { | 376 | 22.6M | result.push((row, v)); | 377 | 22.6M | } else if v < result[col].1 { | 378 | 1.18M | result[col] = (row, v); | 379 | 1.68M | } | 380 | | } | 381 | 6.38M | finished = i; | 382 | 6.38M | continue; | 383 | 16.2M | } | 384 | | | 385 | | // Second case: the new column minimum is on the diagonal. All | 386 | | // subsequent ones will be at least as low, so we can clear | 387 | | // out all our work from higher rows. As in the fourth case, | 388 | | // the loss of tentative is amortized against the increase in | 389 | | // base. | 390 | 16.2M | let diag = m![i - 1, i]; | 391 | 16.2M | if diag < result[i].1 { | 392 | 6.12M | result[i] = (i - 1, diag); | 393 | 6.12M | base = i - 1; | 394 | 6.12M | tentative = i; | 395 | 6.12M | finished = i; | 396 | 6.12M | continue; | 397 | 10.1M | } | 398 | 10.1M | | 399 | 10.1M | // Third case: row i-1 does not supply a column minimum in any | 400 | 10.1M | // column up to tentative. We simply advance finished while | 401 | 10.1M | // maintaining the invariant. | 402 | 10.1M | if m![i - 1, tentative] >= result[tentative].1 { | 403 | 10.1M | finished = i; | 404 | 10.1M | continue; | 405 | 25.3k | } | 406 | 25.3k | | 407 | 25.3k | // Fourth and final case: a new column minimum at tentative. | 408 | 25.3k | // This allows us to make progress by incorporating rows prior | 409 | 25.3k | // to finished into the base. The base invariant holds because | 410 | 25.3k | // these rows cannot supply any later column minima. The work | 411 | 25.3k | // done when we last advanced tentative (and undone by this | 412 | 25.3k | // step) can be amortized against the increase in base. | 413 | 25.3k | base = i - 1; | 414 | 25.3k | tentative = i; | 415 | 25.3k | finished = i; | 416 | | } | 417 | | | 418 | 449k | result | 419 | 449k | } |
smawk::online_column_minima::<f64, textwrap::wrap_algorithms::optimal_fit::wrap_optimal_fit<wrap_optimal_fit_usize::Word>::{closure#0}> Line | Count | Source | 330 | 883 | pub fn online_column_minima<T: Copy + PartialOrd, M: Fn(&[(usize, T)], usize, usize) -> T>( | 331 | 883 | initial: T, | 332 | 883 | size: usize, | 333 | 883 | matrix: M, | 334 | 883 | ) -> Vec<(usize, T)> { | 335 | 883 | let mut result = vec![(0, initial)]; | 336 | 883 | | 337 | 883 | // State used by the algorithm. | 338 | 883 | let mut finished = 0; | 339 | 883 | let mut base = 0; | 340 | 883 | let mut tentative = 0; | 341 | | | 342 | | // Shorthand for evaluating the matrix. We need a macro here since | 343 | | // we don't want to borrow the result vector. | 344 | | macro_rules! m { | 345 | | ($i:expr, $j:expr) => {{ | 346 | | assert!($i < $j, "(i, j) not above diagonal: ({}, {})", $i, $j); | 347 | | assert!( | 348 | | $i < size && $j < size, | 349 | | "(i, j) out of bounds: ({}, {}), size: {}", | 350 | | $i, | 351 | | $j, | 352 | | size | 353 | | ); | 354 | | matrix(&result[..finished + 1], $i, $j) | 355 | | }}; | 356 | | } | 357 | | | 358 | | // Keep going until we have finished all size columns. Since the | 359 | | // columns are zero-indexed, we're done when finished == size - 1. | 360 | 1.99M | while finished < size - 1 { | 361 | | // First case: we have already advanced past the previous | 362 | | // tentative value. We make a new tentative value by applying | 363 | | // smawk_inner to the largest square submatrix that fits under | 364 | | // the base. | 365 | 1.99M | let i = finished + 1; | 366 | 1.99M | if i > tentative { | 367 | 435k | let rows = (base..finished + 1).collect::<Vec<_>>(); | 368 | 435k | tentative = std::cmp::min(finished + rows.len(), size - 1); | 369 | 435k | let cols = (finished + 1..tentative + 1).collect::<Vec<_>>(); | 370 | 435k | let mut minima = vec![0; tentative + 1]; | 371 | 435k | smawk_inner(&|i, j| m![i, j], &rows, &cols, &mut minima); | 372 | 2.70M | for col in cols { | 373 | 2.27M | let row = minima[col]; | 374 | 2.27M | let v = m![row, col]; | 375 | 2.27M | if col >= result.len() { | 376 | 1.99M | result.push((row, v)); | 377 | 1.99M | } else if v < result[col].1 { | 378 | 248k | result[col] = (row, v); | 379 | 248k | } | 380 | | } | 381 | 435k | finished = i; | 382 | 435k | continue; | 383 | 1.56M | } | 384 | | | 385 | | // Second case: the new column minimum is on the diagonal. All | 386 | | // subsequent ones will be at least as low, so we can clear | 387 | | // out all our work from higher rows. As in the fourth case, | 388 | | // the loss of tentative is amortized against the increase in | 389 | | // base. | 390 | 1.56M | let diag = m![i - 1, i]; | 391 | 1.56M | if diag < result[i].1 { | 392 | 282k | result[i] = (i - 1, diag); | 393 | 282k | base = i - 1; | 394 | 282k | tentative = i; | 395 | 282k | finished = i; | 396 | 282k | continue; | 397 | 1.27M | } | 398 | 1.27M | | 399 | 1.27M | // Third case: row i-1 does not supply a column minimum in any | 400 | 1.27M | // column up to tentative. We simply advance finished while | 401 | 1.27M | // maintaining the invariant. | 402 | 1.27M | if m![i - 1, tentative] >= result[tentative].1 { | 403 | 1.25M | finished = i; | 404 | 1.25M | continue; | 405 | 22.0k | } | 406 | 22.0k | | 407 | 22.0k | // Fourth and final case: a new column minimum at tentative. | 408 | 22.0k | // This allows us to make progress by incorporating rows prior | 409 | 22.0k | // to finished into the base. The base invariant holds because | 410 | 22.0k | // these rows cannot supply any later column minima. The work | 411 | 22.0k | // done when we last advanced tentative (and undone by this | 412 | 22.0k | // step) can be amortized against the increase in base. | 413 | 22.0k | base = i - 1; | 414 | 22.0k | tentative = i; | 415 | 22.0k | finished = i; | 416 | | } | 417 | | | 418 | 883 | result | 419 | 883 | } |
smawk::online_column_minima::<f64, textwrap::wrap_algorithms::optimal_fit::wrap_optimal_fit<wrap_optimal_fit::Word>::{closure#0}> Line | Count | Source | 330 | 772 | pub fn online_column_minima<T: Copy + PartialOrd, M: Fn(&[(usize, T)], usize, usize) -> T>( | 331 | 772 | initial: T, | 332 | 772 | size: usize, | 333 | 772 | matrix: M, | 334 | 772 | ) -> Vec<(usize, T)> { | 335 | 772 | let mut result = vec![(0, initial)]; | 336 | 772 | | 337 | 772 | // State used by the algorithm. | 338 | 772 | let mut finished = 0; | 339 | 772 | let mut base = 0; | 340 | 772 | let mut tentative = 0; | 341 | | | 342 | | // Shorthand for evaluating the matrix. We need a macro here since | 343 | | // we don't want to borrow the result vector. | 344 | | macro_rules! m { | 345 | | ($i:expr, $j:expr) => {{ | 346 | | assert!($i < $j, "(i, j) not above diagonal: ({}, {})", $i, $j); | 347 | | assert!( | 348 | | $i < size && $j < size, | 349 | | "(i, j) out of bounds: ({}, {}), size: {}", | 350 | | $i, | 351 | | $j, | 352 | | size | 353 | | ); | 354 | | matrix(&result[..finished + 1], $i, $j) | 355 | | }}; | 356 | | } | 357 | | | 358 | | // Keep going until we have finished all size columns. Since the | 359 | | // columns are zero-indexed, we're done when finished == size - 1. | 360 | 1.84M | while finished < size - 1 { | 361 | | // First case: we have already advanced past the previous | 362 | | // tentative value. We make a new tentative value by applying | 363 | | // smawk_inner to the largest square submatrix that fits under | 364 | | // the base. | 365 | 1.84M | let i = finished + 1; | 366 | 1.84M | if i > tentative { | 367 | 229k | let rows = (base..finished + 1).collect::<Vec<_>>(); | 368 | 229k | tentative = std::cmp::min(finished + rows.len(), size - 1); | 369 | 229k | let cols = (finished + 1..tentative + 1).collect::<Vec<_>>(); | 370 | 229k | let mut minima = vec![0; tentative + 1]; | 371 | 229k | smawk_inner(&|i, j| m![i, j], &rows, &cols, &mut minima); | 372 | 2.45M | for col in cols { | 373 | 2.22M | let row = minima[col]; | 374 | 2.22M | let v = m![row, col]; | 375 | 2.22M | if col >= result.len() { | 376 | 1.84M | result.push((row, v)); | 377 | 1.84M | } else if v < result[col].1 { | 378 | 330k | result[col] = (row, v); | 379 | 330k | } | 380 | | } | 381 | 229k | finished = i; | 382 | 229k | continue; | 383 | 1.61M | } | 384 | | | 385 | | // Second case: the new column minimum is on the diagonal. All | 386 | | // subsequent ones will be at least as low, so we can clear | 387 | | // out all our work from higher rows. As in the fourth case, | 388 | | // the loss of tentative is amortized against the increase in | 389 | | // base. | 390 | 1.61M | let diag = m![i - 1, i]; | 391 | 1.61M | if diag < result[i].1 { | 392 | 165k | result[i] = (i - 1, diag); | 393 | 165k | base = i - 1; | 394 | 165k | tentative = i; | 395 | 165k | finished = i; | 396 | 165k | continue; | 397 | 1.45M | } | 398 | 1.45M | | 399 | 1.45M | // Third case: row i-1 does not supply a column minimum in any | 400 | 1.45M | // column up to tentative. We simply advance finished while | 401 | 1.45M | // maintaining the invariant. | 402 | 1.45M | if m![i - 1, tentative] >= result[tentative].1 { | 403 | 1.43M | finished = i; | 404 | 1.43M | continue; | 405 | 13.9k | } | 406 | 13.9k | | 407 | 13.9k | // Fourth and final case: a new column minimum at tentative. | 408 | 13.9k | // This allows us to make progress by incorporating rows prior | 409 | 13.9k | // to finished into the base. The base invariant holds because | 410 | 13.9k | // these rows cannot supply any later column minima. The work | 411 | 13.9k | // done when we last advanced tentative (and undone by this | 412 | 13.9k | // step) can be amortized against the increase in base. | 413 | 13.9k | base = i - 1; | 414 | 13.9k | tentative = i; | 415 | 13.9k | finished = i; | 416 | | } | 417 | | | 418 | 772 | result | 419 | 772 | } |
|
420 | | |
421 | | #[cfg(test)] |
422 | | mod tests { |
423 | | use super::*; |
424 | | |
425 | | #[test] |
426 | | fn smawk_1x1() { |
427 | | let matrix = vec![vec![2]]; |
428 | | assert_eq!(row_minima(&matrix), vec![0]); |
429 | | assert_eq!(column_minima(&matrix), vec![0]); |
430 | | } |
431 | | |
432 | | #[test] |
433 | | fn smawk_2x1() { |
434 | | let matrix = vec![ |
435 | | vec![3], // |
436 | | vec![2], |
437 | | ]; |
438 | | assert_eq!(row_minima(&matrix), vec![0, 0]); |
439 | | assert_eq!(column_minima(&matrix), vec![1]); |
440 | | } |
441 | | |
442 | | #[test] |
443 | | fn smawk_1x2() { |
444 | | let matrix = vec![vec![2, 1]]; |
445 | | assert_eq!(row_minima(&matrix), vec![1]); |
446 | | assert_eq!(column_minima(&matrix), vec![0, 0]); |
447 | | } |
448 | | |
449 | | #[test] |
450 | | fn smawk_2x2() { |
451 | | let matrix = vec![ |
452 | | vec![3, 2], // |
453 | | vec![2, 1], |
454 | | ]; |
455 | | assert_eq!(row_minima(&matrix), vec![1, 1]); |
456 | | assert_eq!(column_minima(&matrix), vec![1, 1]); |
457 | | } |
458 | | |
459 | | #[test] |
460 | | fn smawk_3x3() { |
461 | | let matrix = vec![ |
462 | | vec![3, 4, 4], // |
463 | | vec![3, 4, 4], |
464 | | vec![2, 3, 3], |
465 | | ]; |
466 | | assert_eq!(row_minima(&matrix), vec![0, 0, 0]); |
467 | | assert_eq!(column_minima(&matrix), vec![2, 2, 2]); |
468 | | } |
469 | | |
470 | | #[test] |
471 | | fn smawk_4x4() { |
472 | | let matrix = vec![ |
473 | | vec![4, 5, 5, 5], // |
474 | | vec![2, 3, 3, 3], |
475 | | vec![2, 3, 3, 3], |
476 | | vec![2, 2, 2, 2], |
477 | | ]; |
478 | | assert_eq!(row_minima(&matrix), vec![0, 0, 0, 0]); |
479 | | assert_eq!(column_minima(&matrix), vec![1, 3, 3, 3]); |
480 | | } |
481 | | |
482 | | #[test] |
483 | | fn smawk_5x5() { |
484 | | let matrix = vec![ |
485 | | vec![3, 2, 4, 5, 6], |
486 | | vec![2, 1, 3, 3, 4], |
487 | | vec![2, 1, 3, 3, 4], |
488 | | vec![3, 2, 4, 3, 4], |
489 | | vec![4, 3, 2, 1, 1], |
490 | | ]; |
491 | | assert_eq!(row_minima(&matrix), vec![1, 1, 1, 1, 3]); |
492 | | assert_eq!(column_minima(&matrix), vec![1, 1, 4, 4, 4]); |
493 | | } |
494 | | |
495 | | #[test] |
496 | | fn online_1x1() { |
497 | | let matrix = vec![vec![0]]; |
498 | | let minima = vec![(0, 0)]; |
499 | | assert_eq!(online_column_minima(0, 1, |_, i, j| matrix[i][j]), minima); |
500 | | } |
501 | | |
502 | | #[test] |
503 | | fn online_2x2() { |
504 | | let matrix = vec![ |
505 | | vec![0, 2], // |
506 | | vec![0, 0], |
507 | | ]; |
508 | | let minima = vec![(0, 0), (0, 2)]; |
509 | | assert_eq!(online_column_minima(0, 2, |_, i, j| matrix[i][j]), minima); |
510 | | } |
511 | | |
512 | | #[test] |
513 | | fn online_3x3() { |
514 | | let matrix = vec![ |
515 | | vec![0, 4, 4], // |
516 | | vec![0, 0, 4], |
517 | | vec![0, 0, 0], |
518 | | ]; |
519 | | let minima = vec![(0, 0), (0, 4), (0, 4)]; |
520 | | assert_eq!(online_column_minima(0, 3, |_, i, j| matrix[i][j]), minima); |
521 | | } |
522 | | |
523 | | #[test] |
524 | | fn online_4x4() { |
525 | | let matrix = vec![ |
526 | | vec![0, 5, 5, 5], // |
527 | | vec![0, 0, 3, 3], |
528 | | vec![0, 0, 0, 3], |
529 | | vec![0, 0, 0, 0], |
530 | | ]; |
531 | | let minima = vec![(0, 0), (0, 5), (1, 3), (1, 3)]; |
532 | | assert_eq!(online_column_minima(0, 4, |_, i, j| matrix[i][j]), minima); |
533 | | } |
534 | | |
535 | | #[test] |
536 | | fn online_5x5() { |
537 | | let matrix = vec![ |
538 | | vec![0, 2, 4, 6, 7], |
539 | | vec![0, 0, 3, 4, 5], |
540 | | vec![0, 0, 0, 3, 4], |
541 | | vec![0, 0, 0, 0, 4], |
542 | | vec![0, 0, 0, 0, 0], |
543 | | ]; |
544 | | let minima = vec![(0, 0), (0, 2), (1, 3), (2, 3), (2, 4)]; |
545 | | assert_eq!(online_column_minima(0, 5, |_, i, j| matrix[i][j]), minima); |
546 | | } |
547 | | |
548 | | #[test] |
549 | | fn smawk_works_with_partial_ord() { |
550 | | let matrix = vec![ |
551 | | vec![3.0, 2.0], // |
552 | | vec![2.0, 1.0], |
553 | | ]; |
554 | | assert_eq!(row_minima(&matrix), vec![1, 1]); |
555 | | assert_eq!(column_minima(&matrix), vec![1, 1]); |
556 | | } |
557 | | |
558 | | #[test] |
559 | | fn online_works_with_partial_ord() { |
560 | | let matrix = vec![ |
561 | | vec![0.0, 2.0], // |
562 | | vec![0.0, 0.0], |
563 | | ]; |
564 | | let minima = vec![(0, 0.0), (0, 2.0)]; |
565 | | assert_eq!( |
566 | | online_column_minima(0.0, 2, |_, i: usize, j: usize| matrix[i][j]), |
567 | | minima |
568 | | ); |
569 | | } |
570 | | } |