/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 | 24.1M | fn smawk_inner<T: PartialOrd + Copy, M: Fn(usize, usize) -> T>( |
251 | 24.1M | matrix: &M, |
252 | 24.1M | rows: &[usize], |
253 | 24.1M | cols: &[usize], |
254 | 24.1M | minima: &mut [usize], |
255 | 24.1M | ) { |
256 | 24.1M | if cols.is_empty() { |
257 | 7.83M | return; |
258 | 16.2M | } |
259 | 16.2M | |
260 | 16.2M | let mut stack = Vec::with_capacity(cols.len()); |
261 | 97.1M | for r in rows { |
262 | | // TODO: use stack.last() instead of stack.is_empty() etc |
263 | 96.6M | while !stack.is_empty() |
264 | 72.2M | && matrix(stack[stack.len() - 1], cols[stack.len() - 1]) |
265 | 72.2M | > matrix(*r, cols[stack.len() - 1]) |
266 | 15.7M | { |
267 | 15.7M | stack.pop(); |
268 | 15.7M | } |
269 | 80.9M | if stack.len() != cols.len() { |
270 | 65.4M | stack.push(*r); |
271 | 65.4M | } |
272 | | } |
273 | 16.2M | let rows = &stack; |
274 | 16.2M | |
275 | 16.2M | let mut odd_cols = Vec::with_capacity(1 + cols.len() / 2); |
276 | 61.0M | for (idx, c) in cols.iter().enumerate() { |
277 | 61.0M | if idx % 2 == 1 { |
278 | 26.5M | odd_cols.push(*c); |
279 | 34.4M | } |
280 | | } |
281 | | |
282 | 16.2M | smawk_inner(matrix, rows, &odd_cols, minima); |
283 | 16.2M | |
284 | 16.2M | let mut r = 0; |
285 | 61.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 | 52.1M | 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.49M | 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.40M | for (c, &col) in cols.iter().enumerate().filter(|(c, _)| c % 2 == 0) { |
|
286 | 34.4M | let mut row = rows[r]; |
287 | 34.4M | let last_row = if c == cols.len() - 1 { |
288 | 7.84M | rows[rows.len() - 1] |
289 | | } else { |
290 | 26.5M | minima[cols[c + 1]] |
291 | | }; |
292 | 34.4M | let mut pair = (matrix(row, col), row); |
293 | 43.0M | while row != last_row { |
294 | 8.57M | r += 1; |
295 | 8.57M | row = rows[r]; |
296 | 8.57M | if (matrix(row, col), row) < pair { |
297 | 1.53M | pair = (matrix(row, col), row); |
298 | 7.03M | } |
299 | | } |
300 | 34.4M | minima[col] = pair.1; |
301 | | } |
302 | 24.1M | } 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 | 21.6M | fn smawk_inner<T: PartialOrd + Copy, M: Fn(usize, usize) -> T>( | 251 | 21.6M | matrix: &M, | 252 | 21.6M | rows: &[usize], | 253 | 21.6M | cols: &[usize], | 254 | 21.6M | minima: &mut [usize], | 255 | 21.6M | ) { | 256 | 21.6M | if cols.is_empty() { | 257 | 7.09M | return; | 258 | 14.5M | } | 259 | 14.5M | | 260 | 14.5M | let mut stack = Vec::with_capacity(cols.len()); | 261 | 82.5M | for r in rows { | 262 | | // TODO: use stack.last() instead of stack.is_empty() etc | 263 | 82.6M | while !stack.is_empty() | 264 | 60.7M | && matrix(stack[stack.len() - 1], cols[stack.len() - 1]) | 265 | 60.7M | > matrix(*r, cols[stack.len() - 1]) | 266 | 14.6M | { | 267 | 14.6M | stack.pop(); | 268 | 14.6M | } | 269 | 68.0M | if stack.len() != cols.len() { | 270 | 56.4M | stack.push(*r); | 271 | 56.4M | } | 272 | | } | 273 | 14.5M | let rows = &stack; | 274 | 14.5M | | 275 | 14.5M | let mut odd_cols = Vec::with_capacity(1 + cols.len() / 2); | 276 | 52.1M | for (idx, c) in cols.iter().enumerate() { | 277 | 52.1M | if idx % 2 == 1 { | 278 | 22.5M | odd_cols.push(*c); | 279 | 29.6M | } | 280 | | } | 281 | | | 282 | 14.5M | smawk_inner(matrix, rows, &odd_cols, minima); | 283 | 14.5M | | 284 | 14.5M | let mut r = 0; | 285 | 29.6M | for (c, &col) in cols.iter().enumerate().filter(|(c, _)| c % 2 == 0) { | 286 | 29.6M | let mut row = rows[r]; | 287 | 29.6M | let last_row = if c == cols.len() - 1 { | 288 | 7.10M | rows[rows.len() - 1] | 289 | | } else { | 290 | 22.5M | minima[cols[c + 1]] | 291 | | }; | 292 | 29.6M | let mut pair = (matrix(row, col), row); | 293 | 37.3M | while row != last_row { | 294 | 7.70M | r += 1; | 295 | 7.70M | row = rows[r]; | 296 | 7.70M | if (matrix(row, col), row) < pair { | 297 | 1.49M | pair = (matrix(row, col), row); | 298 | 6.20M | } | 299 | | } | 300 | 29.6M | minima[col] = pair.1; | 301 | | } | 302 | 21.6M | } |
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.57M | fn smawk_inner<T: PartialOrd + Copy, M: Fn(usize, usize) -> T>( | 251 | 1.57M | matrix: &M, | 252 | 1.57M | rows: &[usize], | 253 | 1.57M | cols: &[usize], | 254 | 1.57M | minima: &mut [usize], | 255 | 1.57M | ) { | 256 | 1.57M | if cols.is_empty() { | 257 | 477k | return; | 258 | 1.09M | } | 259 | 1.09M | | 260 | 1.09M | let mut stack = Vec::with_capacity(cols.len()); | 261 | 7.49M | for r in rows { | 262 | | // TODO: use stack.last() instead of stack.is_empty() etc | 263 | 7.08M | while !stack.is_empty() | 264 | 5.40M | && matrix(stack[stack.len() - 1], cols[stack.len() - 1]) | 265 | 5.40M | > matrix(*r, cols[stack.len() - 1]) | 266 | 688k | { | 267 | 688k | stack.pop(); | 268 | 688k | } | 269 | 6.39M | if stack.len() != cols.len() { | 270 | 4.53M | stack.push(*r); | 271 | 4.53M | } | 272 | | } | 273 | 1.09M | let rows = &stack; | 274 | 1.09M | | 275 | 1.09M | let mut odd_cols = Vec::with_capacity(1 + cols.len() / 2); | 276 | 4.49M | for (idx, c) in cols.iter().enumerate() { | 277 | 4.49M | if idx % 2 == 1 { | 278 | 2.00M | odd_cols.push(*c); | 279 | 2.48M | } | 280 | | } | 281 | | | 282 | 1.09M | smawk_inner(matrix, rows, &odd_cols, minima); | 283 | 1.09M | | 284 | 1.09M | let mut r = 0; | 285 | 2.48M | for (c, &col) in cols.iter().enumerate().filter(|(c, _)| c % 2 == 0) { | 286 | 2.48M | let mut row = rows[r]; | 287 | 2.48M | let last_row = if c == cols.len() - 1 { | 288 | 477k | rows[rows.len() - 1] | 289 | | } else { | 290 | 2.00M | minima[cols[c + 1]] | 291 | | }; | 292 | 2.48M | let mut pair = (matrix(row, col), row); | 293 | 2.87M | while row != last_row { | 294 | 393k | r += 1; | 295 | 393k | row = rows[r]; | 296 | 393k | if (matrix(row, col), row) < pair { | 297 | 13.1k | pair = (matrix(row, col), row); | 298 | 380k | } | 299 | | } | 300 | 2.48M | minima[col] = pair.1; | 301 | | } | 302 | 1.57M | } |
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 | 894k | fn smawk_inner<T: PartialOrd + Copy, M: Fn(usize, usize) -> T>( | 251 | 894k | matrix: &M, | 252 | 894k | rows: &[usize], | 253 | 894k | cols: &[usize], | 254 | 894k | minima: &mut [usize], | 255 | 894k | ) { | 256 | 894k | if cols.is_empty() { | 257 | 261k | return; | 258 | 632k | } | 259 | 632k | | 260 | 632k | let mut stack = Vec::with_capacity(cols.len()); | 261 | 7.12M | for r in rows { | 262 | | // TODO: use stack.last() instead of stack.is_empty() etc | 263 | 6.87M | while !stack.is_empty() | 264 | 6.01M | && matrix(stack[stack.len() - 1], cols[stack.len() - 1]) | 265 | 6.01M | > matrix(*r, cols[stack.len() - 1]) | 266 | 382k | { | 267 | 382k | stack.pop(); | 268 | 382k | } | 269 | 6.49M | if stack.len() != cols.len() { | 270 | 4.44M | stack.push(*r); | 271 | 4.44M | } | 272 | | } | 273 | 632k | let rows = &stack; | 274 | 632k | | 275 | 632k | let mut odd_cols = Vec::with_capacity(1 + cols.len() / 2); | 276 | 4.40M | for (idx, c) in cols.iter().enumerate() { | 277 | 4.40M | if idx % 2 == 1 { | 278 | 2.07M | odd_cols.push(*c); | 279 | 2.33M | } | 280 | | } | 281 | | | 282 | 632k | smawk_inner(matrix, rows, &odd_cols, minima); | 283 | 632k | | 284 | 632k | let mut r = 0; | 285 | 2.33M | for (c, &col) in cols.iter().enumerate().filter(|(c, _)| c % 2 == 0) { | 286 | 2.33M | let mut row = rows[r]; | 287 | 2.33M | let last_row = if c == cols.len() - 1 { | 288 | 262k | rows[rows.len() - 1] | 289 | | } else { | 290 | 2.07M | minima[cols[c + 1]] | 291 | | }; | 292 | 2.33M | let mut pair = (matrix(row, col), row); | 293 | 2.81M | while row != last_row { | 294 | 476k | r += 1; | 295 | 476k | row = rows[r]; | 296 | 476k | if (matrix(row, col), row) < pair { | 297 | 28.2k | pair = (matrix(row, col), row); | 298 | 448k | } | 299 | | } | 300 | 2.33M | minima[col] = pair.1; | 301 | | } | 302 | 894k | } |
|
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 | 317k | pub fn online_column_minima<T: Copy + PartialOrd, M: Fn(&[(usize, T)], usize, usize) -> T>( |
331 | 317k | initial: T, |
332 | 317k | size: usize, |
333 | 317k | matrix: M, |
334 | 317k | ) -> Vec<(usize, T)> { |
335 | 317k | let mut result = vec![(0, initial)]; |
336 | 317k | |
337 | 317k | // State used by the algorithm. |
338 | 317k | let mut finished = 0; |
339 | 317k | let mut base = 0; |
340 | 317k | 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 | 30.5M | 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 | 30.2M | let i = finished + 1; |
366 | 30.2M | if i > tentative { |
367 | 7.83M | let rows = (base..finished + 1).collect::<Vec<_>>(); |
368 | 7.83M | tentative = std::cmp::min(finished + rows.len(), size - 1); |
369 | 7.83M | let cols = (finished + 1..tentative + 1).collect::<Vec<_>>(); |
370 | 7.83M | let mut minima = vec![0; tentative + 1]; |
371 | 188M | 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 | 160M | 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 | 13.7M | 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.8M | smawk_inner(&|i, j| m![i, j], &rows, &cols, &mut minima); |
|
372 | 42.2M | for col in cols { |
373 | 34.4M | let row = minima[col]; |
374 | 34.4M | let v = m![row, col]; |
375 | 34.4M | if col >= result.len() { |
376 | 30.2M | result.push((row, v)); |
377 | 30.2M | } else if v < result[col].1 { |
378 | 1.92M | result[col] = (row, v); |
379 | 2.23M | } |
380 | | } |
381 | 7.83M | finished = i; |
382 | 7.83M | continue; |
383 | 22.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 | 22.4M | let diag = m![i - 1, i]; |
391 | 22.4M | if diag < result[i].1 { |
392 | 7.34M | result[i] = (i - 1, diag); |
393 | 7.34M | base = i - 1; |
394 | 7.34M | tentative = i; |
395 | 7.34M | finished = i; |
396 | 7.34M | continue; |
397 | 15.0M | } |
398 | 15.0M | |
399 | 15.0M | // Third case: row i-1 does not supply a column minimum in any |
400 | 15.0M | // column up to tentative. We simply advance finished while |
401 | 15.0M | // maintaining the invariant. |
402 | 15.0M | if m![i - 1, tentative] >= result[tentative].1 { |
403 | 15.0M | finished = i; |
404 | 15.0M | continue; |
405 | 79.5k | } |
406 | 79.5k | |
407 | 79.5k | // Fourth and final case: a new column minimum at tentative. |
408 | 79.5k | // This allows us to make progress by incorporating rows prior |
409 | 79.5k | // to finished into the base. The base invariant holds because |
410 | 79.5k | // these rows cannot supply any later column minima. The work |
411 | 79.5k | // done when we last advanced tentative (and undone by this |
412 | 79.5k | // step) can be amortized against the increase in base. |
413 | 79.5k | base = i - 1; |
414 | 79.5k | tentative = i; |
415 | 79.5k | finished = i; |
416 | | } |
417 | | |
418 | 317k | result |
419 | 317k | } smawk::online_column_minima::<f64, textwrap::wrap_algorithms::optimal_fit::wrap_optimal_fit<textwrap::core::Word>::{closure#0}> Line | Count | Source | 330 | 316k | pub fn online_column_minima<T: Copy + PartialOrd, M: Fn(&[(usize, T)], usize, usize) -> T>( | 331 | 316k | initial: T, | 332 | 316k | size: usize, | 333 | 316k | matrix: M, | 334 | 316k | ) -> Vec<(usize, T)> { | 335 | 316k | let mut result = vec![(0, initial)]; | 336 | 316k | | 337 | 316k | // State used by the algorithm. | 338 | 316k | let mut finished = 0; | 339 | 316k | let mut base = 0; | 340 | 316k | 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.4M | 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.1M | let i = finished + 1; | 366 | 26.1M | if i > tentative { | 367 | 7.09M | let rows = (base..finished + 1).collect::<Vec<_>>(); | 368 | 7.09M | tentative = std::cmp::min(finished + rows.len(), size - 1); | 369 | 7.09M | let cols = (finished + 1..tentative + 1).collect::<Vec<_>>(); | 370 | 7.09M | let mut minima = vec![0; tentative + 1]; | 371 | 7.09M | smawk_inner(&|i, j| m![i, j], &rows, &cols, &mut minima); | 372 | 36.7M | for col in cols { | 373 | 29.6M | let row = minima[col]; | 374 | 29.6M | let v = m![row, col]; | 375 | 29.6M | if col >= result.len() { | 376 | 26.1M | result.push((row, v)); | 377 | 26.1M | } else if v < result[col].1 { | 378 | 1.33M | result[col] = (row, v); | 379 | 2.15M | } | 380 | | } | 381 | 7.09M | finished = i; | 382 | 7.09M | continue; | 383 | 19.0M | } | 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.0M | let diag = m![i - 1, i]; | 391 | 19.0M | if diag < result[i].1 { | 392 | 6.85M | result[i] = (i - 1, diag); | 393 | 6.85M | base = i - 1; | 394 | 6.85M | tentative = i; | 395 | 6.85M | finished = i; | 396 | 6.85M | continue; | 397 | 12.1M | } | 398 | 12.1M | | 399 | 12.1M | // Third case: row i-1 does not supply a column minimum in any | 400 | 12.1M | // column up to tentative. We simply advance finished while | 401 | 12.1M | // maintaining the invariant. | 402 | 12.1M | if m![i - 1, tentative] >= result[tentative].1 { | 403 | 12.1M | finished = i; | 404 | 12.1M | continue; | 405 | 26.1k | } | 406 | 26.1k | | 407 | 26.1k | // Fourth and final case: a new column minimum at tentative. | 408 | 26.1k | // This allows us to make progress by incorporating rows prior | 409 | 26.1k | // to finished into the base. The base invariant holds because | 410 | 26.1k | // these rows cannot supply any later column minima. The work | 411 | 26.1k | // done when we last advanced tentative (and undone by this | 412 | 26.1k | // step) can be amortized against the increase in base. | 413 | 26.1k | base = i - 1; | 414 | 26.1k | tentative = i; | 415 | 26.1k | finished = i; | 416 | | } | 417 | | | 418 | 316k | result | 419 | 316k | } |
smawk::online_column_minima::<f64, textwrap::wrap_algorithms::optimal_fit::wrap_optimal_fit<wrap_optimal_fit_usize::Word>::{closure#0}> Line | Count | Source | 330 | 726 | pub fn online_column_minima<T: Copy + PartialOrd, M: Fn(&[(usize, T)], usize, usize) -> T>( | 331 | 726 | initial: T, | 332 | 726 | size: usize, | 333 | 726 | matrix: M, | 334 | 726 | ) -> Vec<(usize, T)> { | 335 | 726 | let mut result = vec![(0, initial)]; | 336 | 726 | | 337 | 726 | // State used by the algorithm. | 338 | 726 | let mut finished = 0; | 339 | 726 | let mut base = 0; | 340 | 726 | 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 | 2.22M | 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 | 2.21M | let i = finished + 1; | 366 | 2.21M | if i > tentative { | 367 | 477k | let rows = (base..finished + 1).collect::<Vec<_>>(); | 368 | 477k | tentative = std::cmp::min(finished + rows.len(), size - 1); | 369 | 477k | let cols = (finished + 1..tentative + 1).collect::<Vec<_>>(); | 370 | 477k | let mut minima = vec![0; tentative + 1]; | 371 | 477k | smawk_inner(&|i, j| m![i, j], &rows, &cols, &mut minima); | 372 | 2.96M | for col in cols { | 373 | 2.48M | let row = minima[col]; | 374 | 2.48M | let v = m![row, col]; | 375 | 2.48M | if col >= result.len() { | 376 | 2.21M | result.push((row, v)); | 377 | 2.21M | } else if v < result[col].1 { | 378 | 241k | result[col] = (row, v); | 379 | 241k | } | 380 | | } | 381 | 477k | finished = i; | 382 | 477k | continue; | 383 | 1.74M | } | 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.74M | let diag = m![i - 1, i]; | 391 | 1.74M | if diag < result[i].1 { | 392 | 322k | result[i] = (i - 1, diag); | 393 | 322k | base = i - 1; | 394 | 322k | tentative = i; | 395 | 322k | finished = i; | 396 | 322k | continue; | 397 | 1.42M | } | 398 | 1.42M | | 399 | 1.42M | // Third case: row i-1 does not supply a column minimum in any | 400 | 1.42M | // column up to tentative. We simply advance finished while | 401 | 1.42M | // maintaining the invariant. | 402 | 1.42M | if m![i - 1, tentative] >= result[tentative].1 { | 403 | 1.39M | finished = i; | 404 | 1.39M | continue; | 405 | 27.7k | } | 406 | 27.7k | | 407 | 27.7k | // Fourth and final case: a new column minimum at tentative. | 408 | 27.7k | // This allows us to make progress by incorporating rows prior | 409 | 27.7k | // to finished into the base. The base invariant holds because | 410 | 27.7k | // these rows cannot supply any later column minima. The work | 411 | 27.7k | // done when we last advanced tentative (and undone by this | 412 | 27.7k | // step) can be amortized against the increase in base. | 413 | 27.7k | base = i - 1; | 414 | 27.7k | tentative = i; | 415 | 27.7k | finished = i; | 416 | | } | 417 | | | 418 | 726 | result | 419 | 726 | } |
smawk::online_column_minima::<f64, textwrap::wrap_algorithms::optimal_fit::wrap_optimal_fit<wrap_optimal_fit::Word>::{closure#0}> Line | Count | Source | 330 | 665 | pub fn online_column_minima<T: Copy + PartialOrd, M: Fn(&[(usize, T)], usize, usize) -> T>( | 331 | 665 | initial: T, | 332 | 665 | size: usize, | 333 | 665 | matrix: M, | 334 | 665 | ) -> Vec<(usize, T)> { | 335 | 665 | let mut result = vec![(0, initial)]; | 336 | 665 | | 337 | 665 | // State used by the algorithm. | 338 | 665 | let mut finished = 0; | 339 | 665 | let mut base = 0; | 340 | 665 | 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.92M | 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.92M | let i = finished + 1; | 366 | 1.92M | if i > tentative { | 367 | 261k | let rows = (base..finished + 1).collect::<Vec<_>>(); | 368 | 261k | tentative = std::cmp::min(finished + rows.len(), size - 1); | 369 | 261k | let cols = (finished + 1..tentative + 1).collect::<Vec<_>>(); | 370 | 261k | let mut minima = vec![0; tentative + 1]; | 371 | 261k | smawk_inner(&|i, j| m![i, j], &rows, &cols, &mut minima); | 372 | 2.59M | for col in cols { | 373 | 2.33M | let row = minima[col]; | 374 | 2.33M | let v = m![row, col]; | 375 | 2.33M | if col >= result.len() { | 376 | 1.92M | result.push((row, v)); | 377 | 1.92M | } else if v < result[col].1 { | 378 | 350k | result[col] = (row, v); | 379 | 350k | } | 380 | | } | 381 | 261k | finished = i; | 382 | 261k | continue; | 383 | 1.65M | } | 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.65M | let diag = m![i - 1, i]; | 391 | 1.65M | if diag < result[i].1 { | 392 | 169k | result[i] = (i - 1, diag); | 393 | 169k | base = i - 1; | 394 | 169k | tentative = i; | 395 | 169k | finished = i; | 396 | 169k | continue; | 397 | 1.49M | } | 398 | 1.49M | | 399 | 1.49M | // Third case: row i-1 does not supply a column minimum in any | 400 | 1.49M | // column up to tentative. We simply advance finished while | 401 | 1.49M | // maintaining the invariant. | 402 | 1.49M | if m![i - 1, tentative] >= result[tentative].1 { | 403 | 1.46M | finished = i; | 404 | 1.46M | continue; | 405 | 25.6k | } | 406 | 25.6k | | 407 | 25.6k | // Fourth and final case: a new column minimum at tentative. | 408 | 25.6k | // This allows us to make progress by incorporating rows prior | 409 | 25.6k | // to finished into the base. The base invariant holds because | 410 | 25.6k | // these rows cannot supply any later column minima. The work | 411 | 25.6k | // done when we last advanced tentative (and undone by this | 412 | 25.6k | // step) can be amortized against the increase in base. | 413 | 25.6k | base = i - 1; | 414 | 25.6k | tentative = i; | 415 | 25.6k | finished = i; | 416 | | } | 417 | | | 418 | 665 | result | 419 | 665 | } |
|
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 | | } |