smawk/lib.rs
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//! ## Online Column Minima and Dynamic Programming
90//!
91//! In the standard offline SMAWK algorithm, the entire matrix must be
92//! known and queryable upfront. However, many dynamic programming
93//! problems (like the *Least Weight Subsequence* problem) involve
94//! finding a sequence of indices to minimize a transition cost of the
95//! form:
96//!
97//! ```text
98//! v(0) = initial
99//! v(j) = min { v(i) + w(i, j) | 0 <= i < j } for j > 0
100//! ```
101//!
102//! If the transition weight function `w(i, j)` satisfies the Monge
103//! property, the matrix `M[i, j] = v(i) + w(i, j)` is totally
104//! monotone. However, the matrix entries in column `j` cannot be
105//! computed until the optimal prefix values `v(i)` for all `i < j`
106//! are finalized. This is an *online* dependency constraint.
107//!
108//! The [`online_column_minima`] function solves this online dynamic
109//! programming problem in O(*n*) time. It wraps the core SMAWK
110//! algorithm inside an online check-and-correct harness (based on the
111//! Galil-Park algorithm), executing matrix queries dynamically as the
112//! input prefix calculations are completed.
113//!
114//! A prime practical application of this is the Knuth-Plass paragraph
115//! line-breaking algorithm (used in TeX and crates like `textwrap`).
116//! By framing word wrapping as a concave least weight subsequence
117//! problem, the optimal line breaks of a paragraph of *n* words can
118//! be found in O(*n*) time instead of O(*n*²).
119//!
120//! # References
121//!
122//! - Alok Aggarwal, Maria M. Klawe, Shlomo Moran, Peter Shor, and Robert Wilber.
123//! **Geometric applications of a matrix searching algorithm**.
124//! *Algorithmica*, 2(1):195–208, 1987.
125//! - Robert Wilber. **The concave least-weight subsequence problem
126//! revisited**. *Journal of Algorithms*, 9(3):418–425, 1988.
127//! - Zvi Galil and Kunsoo Park. **A linear-time algorithm for concave
128//! one-dimensional dynamic programming**. *Information Processing Letters*,
129//! 33(6):309–313, 1990.
130//! - Donald E. Knuth and Michael F. Plass. **Breaking paragraphs into lines**.
131//! *Software: Practice and Experience*, 11(11):1119–1184, 1981.
132
133#![no_std]
134#![doc(html_root_url = "https://docs.rs/smawk/0.3.3")]
135// The s! macro from ndarray uses unsafe internally, so we can only
136// forbid unsafe code when building with the default features.
137#![cfg_attr(not(feature = "ndarray"), forbid(unsafe_code))]
138
139extern crate alloc;
140use alloc::vec;
141use alloc::vec::Vec;
142
143#[cfg(feature = "ndarray")]
144pub mod brute_force;
145pub mod monge;
146#[cfg(feature = "ndarray")]
147pub mod recursive;
148
149/// Minimal matrix trait for two-dimensional arrays.
150///
151/// This provides the functionality needed to represent a read-only
152/// numeric matrix. You can query the size of the matrix and access
153/// elements. Modeled after [`ndarray::Array2`] from the [ndarray
154/// crate](https://crates.io/crates/ndarray).
155///
156/// Enable the `ndarray` Cargo feature if you want to use it with
157/// `ndarray::Array2`.
158pub trait Matrix<T: Copy> {
159 /// Return the number of rows.
160 fn nrows(&self) -> usize;
161 /// Return the number of columns.
162 fn ncols(&self) -> usize;
163 /// Return a matrix element.
164 fn index(&self, row: usize, column: usize) -> T;
165}
166
167/// Simple and inefficient matrix representation used for doctest
168/// examples and simple unit tests.
169///
170/// You should prefer implementing it yourself, or you can enable the
171/// `ndarray` Cargo feature and use the provided implementation for
172/// [`ndarray::Array2`].
173impl<T: Copy> Matrix<T> for Vec<Vec<T>> {
174 fn nrows(&self) -> usize {
175 self.len()
176 }
177 fn ncols(&self) -> usize {
178 self[0].len()
179 }
180 fn index(&self, row: usize, column: usize) -> T {
181 self[row][column]
182 }
183}
184
185/// Adapting [`ndarray::Array2`] to the `Matrix` trait.
186///
187/// **Note: this implementation is only available if you enable the
188/// `ndarray` Cargo feature.**
189#[cfg(feature = "ndarray")]
190impl<T: Copy> Matrix<T> for ndarray::Array2<T> {
191 #[inline]
192 fn nrows(&self) -> usize {
193 self.nrows()
194 }
195 #[inline]
196 fn ncols(&self) -> usize {
197 self.ncols()
198 }
199 #[inline]
200 fn index(&self, row: usize, column: usize) -> T {
201 self[[row, column]]
202 }
203}
204
205/// Compute row minima in O(*m* + *n*) time.
206///
207/// This implements the [SMAWK algorithm] for efficiently finding row
208/// minima in a totally monotone matrix.
209///
210/// The SMAWK algorithm is from Agarwal, Klawe, Moran, Shor, and
211/// Wilbur, *Geometric applications of a matrix searching algorithm*,
212/// Algorithmica 2, pp. 195-208 (1987) and the code here is a
213/// translation [David Eppstein's Python code][pads].
214///
215/// Running time on an *m* × *n* matrix: O(*m* + *n*).
216///
217/// # Examples
218///
219/// ```
220/// use smawk::Matrix;
221/// let matrix = vec![vec![4, 2, 4, 3],
222/// vec![5, 3, 5, 3],
223/// vec![5, 3, 3, 1]];
224/// assert_eq!(smawk::row_minima(&matrix),
225/// vec![1, 1, 3]);
226/// ```
227///
228/// # Panics
229///
230/// It is an error to call this on a matrix with zero columns.
231///
232/// [pads]: https://github.com/jfinkels/PADS/blob/master/pads/smawk.py
233/// [SMAWK algorithm]: https://en.wikipedia.org/wiki/SMAWK_algorithm
234pub fn row_minima<T: PartialOrd + Copy, M: Matrix<T>>(matrix: &M) -> Vec<usize> {
235 // Benchmarking shows that SMAWK performs roughly the same on row-
236 // and column-major matrices.
237 let mut minima = vec![0; matrix.nrows()];
238 let (mut rows_scratch, mut cols_scratch) = scratchpads(matrix.ncols(), matrix.nrows());
239 smawk_inner(
240 &|j, i| matrix.index(i, j),
241 (&mut rows_scratch, 0, matrix.ncols()),
242 (&mut cols_scratch, 0, matrix.nrows()),
243 &mut minima,
244 );
245 minima
246}
247
248#[deprecated(since = "0.3.2", note = "Please use `row_minima` instead.")]
249pub fn smawk_row_minima<T: PartialOrd + Copy, M: Matrix<T>>(matrix: &M) -> Vec<usize> {
250 row_minima(matrix)
251}
252
253/// Compute column minima in O(*m* + *n*) time.
254///
255/// This implements the [SMAWK algorithm] for efficiently finding
256/// column minima in a totally monotone matrix.
257///
258/// The SMAWK algorithm is from Agarwal, Klawe, Moran, Shor, and
259/// Wilbur, *Geometric applications of a matrix searching algorithm*,
260/// Algorithmica 2, pp. 195-208 (1987) and the code here is a
261/// translation [David Eppstein's Python code][pads].
262///
263/// Running time on an *m* × *n* matrix: O(*m* + *n*).
264///
265/// # Examples
266///
267/// ```
268/// use smawk::Matrix;
269/// let matrix = vec![vec![4, 2, 4, 3],
270/// vec![5, 3, 5, 3],
271/// vec![5, 3, 3, 1]];
272/// assert_eq!(smawk::column_minima(&matrix),
273/// vec![0, 0, 2, 2]);
274/// ```
275///
276/// # Panics
277///
278/// It is an error to call this on a matrix with zero rows.
279///
280/// [SMAWK algorithm]: https://en.wikipedia.org/wiki/SMAWK_algorithm
281/// [pads]: https://github.com/jfinkels/PADS/blob/master/pads/smawk.py
282pub fn column_minima<T: PartialOrd + Copy, M: Matrix<T>>(matrix: &M) -> Vec<usize> {
283 let mut minima = vec![0; matrix.ncols()];
284 let (mut rows_scratch, mut cols_scratch) = scratchpads(matrix.nrows(), matrix.ncols());
285 smawk_inner(
286 &|i, j| matrix.index(i, j),
287 (&mut rows_scratch, 0, matrix.nrows()),
288 (&mut cols_scratch, 0, matrix.ncols()),
289 &mut minima,
290 );
291 minima
292}
293
294#[deprecated(since = "0.3.2", note = "Please use `column_minima` instead.")]
295pub fn smawk_column_minima<T: PartialOrd + Copy, M: Matrix<T>>(matrix: &M) -> Vec<usize> {
296 column_minima(matrix)
297}
298
299/// Pre-allocate empty row and column scratchpads with the capacity
300/// needed for the SMAWK algorithm.
301///
302/// ### Scratchpad Capacity
303///
304/// At each recursion level of `smawk_inner` with `R` rows and `C` columns:
305///
306/// 1. We build a stack of survivors by appending up to `C` elements to `rows_scratch`.
307/// 2. We select odd columns by appending `C / 2` elements to `cols_scratch`.
308/// 3. We recurse with `R' <= C` rows and `C' = C / 2` columns.
309///
310/// Summing the maximum elements appended across all recursion levels:
311///
312/// - `rows_scratch` capacity: `R + C + C/2 + C/4 + ... < R + 2 * C`
313/// - `cols_scratch` capacity: `C + C/2 + C/4 + C/8 + ... < 2 * C`
314///
315/// This mathematical guarantee ensures that the scratchpads never need to reallocate/grow.
316#[inline(always)]
317fn scratchpads_empty(nrows: usize, ncols: usize) -> (Vec<usize>, Vec<usize>) {
318 let rows_scratch = vec![0; nrows + 2 * ncols];
319 let cols_scratch = vec![0; ncols * 2];
320 (rows_scratch, cols_scratch)
321}
322
323/// Pre-allocate and populate the row and column scratchpads for the
324/// SMAWK algorithm.
325#[inline(always)]
326fn scratchpads(nrows: usize, ncols: usize) -> (Vec<usize>, Vec<usize>) {
327 let (mut rows_scratch, mut cols_scratch) = scratchpads_empty(nrows, ncols);
328 for (i, val) in rows_scratch.iter_mut().enumerate().take(nrows) {
329 *val = i;
330 }
331 for (i, val) in cols_scratch.iter_mut().enumerate().take(ncols) {
332 *val = i;
333 }
334 (rows_scratch, cols_scratch)
335}
336
337/// Compute column minima in the given area of the matrix. The
338/// `minima` slice is updated inplace.
339fn smawk_inner<T: PartialOrd + Copy, M: Fn(usize, usize) -> T>(
340 matrix: &M,
341 (rows_scratch, rows_start, rows_end): (&mut [usize], usize, usize),
342 (cols_scratch, cols_start, cols_end): (&mut [usize], usize, usize),
343 minima: &mut [usize],
344) {
345 if cols_start == cols_end {
346 return;
347 }
348
349 let cols_len = cols_end - cols_start;
350 let stack_start = rows_end;
351 let odd_cols_start = cols_end;
352 let odd_cols_len = cols_len / 2;
353
354 // Upfront assertions using the maximum index trick. Asserting the
355 // maximum index accessed in each loop/vector beforehand allows
356 // LLVM to prove that all smaller indices accessed in the loop are
357 // in bounds, eliminating individual bounds checking inside the
358 // loops.
359 assert!(rows_end <= rows_scratch.len());
360 assert!(stack_start + cols_len <= rows_scratch.len());
361 assert!(cols_start + 2 * odd_cols_len <= cols_scratch.len());
362 assert!(odd_cols_start + odd_cols_len <= cols_scratch.len());
363
364 let mut stack_len = 0;
365 for i in rows_start..rows_end {
366 let r = rows_scratch[i];
367 while stack_len > 0 {
368 let stack_top = rows_scratch[stack_start + stack_len - 1];
369 let col = cols_scratch[cols_start + stack_len - 1];
370 if matrix(stack_top, col) > matrix(r, col) {
371 stack_len -= 1;
372 } else {
373 break;
374 }
375 }
376 if stack_len < cols_len {
377 rows_scratch[stack_start + stack_len] = r;
378 stack_len += 1;
379 }
380 }
381
382 for idx in 0..odd_cols_len {
383 let col = cols_scratch[cols_start + 2 * idx + 1];
384 cols_scratch[odd_cols_start + idx] = col;
385 }
386
387 smawk_inner(
388 matrix,
389 (&mut *rows_scratch, stack_start, stack_start + stack_len),
390 (
391 &mut *cols_scratch,
392 odd_cols_start,
393 odd_cols_start + odd_cols_len,
394 ),
395 minima,
396 );
397
398 let mut r = 0;
399 // Assert the final stack boundary before the loop.
400 assert!(stack_start + stack_len <= rows_scratch.len());
401 for c in 0..cols_len {
402 if c % 2 == 0 {
403 let col = cols_scratch[cols_start + c];
404 let mut row = rows_scratch[stack_start + r];
405 let last_row = if c == cols_len - 1 {
406 rows_scratch[stack_start + stack_len - 1]
407 } else {
408 minima[cols_scratch[cols_start + c + 1]]
409 };
410 let mut pair = (matrix(row, col), row);
411 while row != last_row && r < stack_len - 1 {
412 r += 1;
413 row = rows_scratch[stack_start + r];
414 if (matrix(row, col), row) < pair {
415 pair = (matrix(row, col), row);
416 }
417 }
418 minima[col] = pair.1;
419 }
420 }
421}
422
423/// Compute upper-right column minima in O(*m* + *n*) time.
424///
425/// The input matrix must be totally monotone.
426///
427/// The function returns a vector of `(usize, T)`. The `usize` in the
428/// tuple at index `j` tells you the row of the minimum value in
429/// column `j` and the `T` value is minimum value itself.
430///
431/// The algorithm only considers values above the main diagonal, which
432/// means that it computes values `v(j)` where:
433///
434/// ```text
435/// v(0) = initial
436/// v(j) = min { M[i, j] | i < j } for j > 0
437/// ```
438///
439/// If we let `r(j)` denote the row index of the minimum value in
440/// column `j`, the tuples in the result vector become `(r(j), M[r(j),
441/// j])`.
442///
443/// The algorithm is an *online* algorithm, in the sense that `matrix`
444/// function can refer back to previously computed column minima when
445/// determining an entry in the matrix. The guarantee is that we only
446/// call `matrix(i, j)` after having computed `v(i)`. This is
447/// reflected in the `&[(usize, T)]` argument to `matrix`, which grows
448/// as more and more values are computed.
449pub fn online_column_minima<T: Copy + PartialOrd, M: Fn(&[(usize, T)], usize, usize) -> T>(
450 initial: T,
451 size: usize,
452 matrix: M,
453) -> Vec<(usize, T)> {
454 let mut result = Vec::with_capacity(size);
455 result.push((0, initial));
456
457 // State used by the algorithm.
458 let mut finished = 0;
459 let mut base = 0;
460 let mut tentative = 0;
461
462 // Shorthand for evaluating the matrix.
463 macro_rules! m {
464 ($i:expr, $j:expr) => {{
465 matrix(&result[..finished + 1], $i, $j)
466 }};
467 }
468
469 let (mut rows_scratch, mut cols_scratch) = scratchpads_empty(size, size);
470 let mut minima = vec![0; size];
471
472 // Keep going until we have finished all size columns. Since the
473 // columns are zero-indexed, we're done when finished == size - 1.
474 while finished < size - 1 {
475 // First case: we have already advanced past the previous
476 // tentative value. We make a new tentative value by applying
477 // smawk_inner to the largest square submatrix that fits under
478 // the base.
479 let i = finished + 1;
480 if i > tentative {
481 let rows_start = 0;
482 let rows_end = finished + 1 - base;
483 for (idx, r) in (base..finished + 1).enumerate() {
484 rows_scratch[idx] = r;
485 }
486
487 tentative = core::cmp::min(finished + rows_end, size - 1);
488
489 let cols_start = 0;
490 let cols_end = tentative - finished;
491 for (idx, c) in (finished + 1..tentative + 1).enumerate() {
492 cols_scratch[idx] = c;
493 }
494
495 smawk_inner(
496 &|i, j| m![i, j],
497 (&mut rows_scratch, rows_start, rows_end),
498 (&mut cols_scratch, cols_start, cols_end),
499 &mut minima,
500 );
501
502 for col in finished + 1..tentative + 1 {
503 let row = minima[col];
504 let v = m![row, col];
505 if col >= result.len() {
506 result.push((row, v));
507 } else if v < result[col].1 {
508 result[col] = (row, v);
509 }
510 }
511
512 finished = i;
513 continue;
514 }
515
516 // Second case: the new column minimum is on the diagonal. All
517 // subsequent ones will be at least as low, so we can clear
518 // out all our work from higher rows. As in the fourth case,
519 // the loss of tentative is amortized against the increase in
520 // base.
521 let diag = m![i - 1, i];
522 if diag < result[i].1 {
523 result[i] = (i - 1, diag);
524 base = i - 1;
525 tentative = i;
526 finished = i;
527 continue;
528 }
529
530 // Third case: row i-1 does not supply a column minimum in any
531 // column up to tentative. We simply advance finished while
532 // maintaining the invariant.
533 if m![i - 1, tentative] >= result[tentative].1 {
534 finished = i;
535 continue;
536 }
537
538 // Fourth and final case: a new column minimum at tentative.
539 // This allows us to make progress by incorporating rows prior
540 // to finished into the base. The base invariant holds because
541 // these rows cannot supply any later column minima. The work
542 // done when we last advanced tentative (and undone by this
543 // step) can be amortized against the increase in base.
544 base = i - 1;
545 tentative = i;
546 finished = i;
547 }
548
549 result
550}
551
552#[cfg(test)]
553mod tests {
554 use super::*;
555
556 #[test]
557 fn smawk_1x1() {
558 let matrix = vec![vec![2]];
559 assert_eq!(row_minima(&matrix), vec![0]);
560 assert_eq!(column_minima(&matrix), vec![0]);
561 }
562
563 #[test]
564 fn smawk_2x1() {
565 let matrix = vec![
566 vec![3], //
567 vec![2],
568 ];
569 assert_eq!(row_minima(&matrix), vec![0, 0]);
570 assert_eq!(column_minima(&matrix), vec![1]);
571 }
572
573 #[test]
574 fn smawk_1x2() {
575 let matrix = vec![vec![2, 1]];
576 assert_eq!(row_minima(&matrix), vec![1]);
577 assert_eq!(column_minima(&matrix), vec![0, 0]);
578 }
579
580 #[test]
581 fn smawk_2x2() {
582 let matrix = vec![
583 vec![3, 2], //
584 vec![2, 1],
585 ];
586 assert_eq!(row_minima(&matrix), vec![1, 1]);
587 assert_eq!(column_minima(&matrix), vec![1, 1]);
588 }
589
590 #[test]
591 fn smawk_3x3() {
592 let matrix = vec![
593 vec![3, 4, 4], //
594 vec![3, 4, 4],
595 vec![2, 3, 3],
596 ];
597 assert_eq!(row_minima(&matrix), vec![0, 0, 0]);
598 assert_eq!(column_minima(&matrix), vec![2, 2, 2]);
599 }
600
601 #[test]
602 fn smawk_4x4() {
603 let matrix = vec![
604 vec![4, 5, 5, 5], //
605 vec![2, 3, 3, 3],
606 vec![2, 3, 3, 3],
607 vec![2, 2, 2, 2],
608 ];
609 assert_eq!(row_minima(&matrix), vec![0, 0, 0, 0]);
610 assert_eq!(column_minima(&matrix), vec![1, 3, 3, 3]);
611 }
612
613 #[test]
614 fn smawk_5x5() {
615 let matrix = vec![
616 vec![3, 2, 4, 5, 6],
617 vec![2, 1, 3, 3, 4],
618 vec![2, 1, 3, 3, 4],
619 vec![3, 2, 4, 3, 4],
620 vec![4, 3, 2, 1, 1],
621 ];
622 assert_eq!(row_minima(&matrix), vec![1, 1, 1, 1, 3]);
623 assert_eq!(column_minima(&matrix), vec![1, 1, 4, 4, 4]);
624 }
625
626 #[test]
627 fn online_1x1() {
628 let matrix = [[0]];
629 let minima = vec![(0, 0)];
630 assert_eq!(online_column_minima(0, 1, |_, i, j| matrix[i][j]), minima);
631 }
632
633 #[test]
634 fn online_2x2() {
635 let matrix = [
636 [0, 2], //
637 [0, 0],
638 ];
639 let minima = vec![(0, 0), (0, 2)];
640 assert_eq!(online_column_minima(0, 2, |_, i, j| matrix[i][j]), minima);
641 }
642
643 #[test]
644 fn online_3x3() {
645 let matrix = [
646 [0, 4, 4], //
647 [0, 0, 4],
648 [0, 0, 0],
649 ];
650 let minima = vec![(0, 0), (0, 4), (0, 4)];
651 assert_eq!(online_column_minima(0, 3, |_, i, j| matrix[i][j]), minima);
652 }
653
654 #[test]
655 fn online_4x4() {
656 let matrix = [
657 [0, 5, 5, 5], //
658 [0, 0, 3, 3],
659 [0, 0, 0, 3],
660 [0, 0, 0, 0],
661 ];
662 let minima = vec![(0, 0), (0, 5), (1, 3), (1, 3)];
663 assert_eq!(online_column_minima(0, 4, |_, i, j| matrix[i][j]), minima);
664 }
665
666 #[test]
667 fn online_5x5() {
668 let matrix = [
669 [0, 2, 4, 6, 7],
670 [0, 0, 3, 4, 5],
671 [0, 0, 0, 3, 4],
672 [0, 0, 0, 0, 4],
673 [0, 0, 0, 0, 0],
674 ];
675 let minima = vec![(0, 0), (0, 2), (1, 3), (2, 3), (2, 4)];
676 assert_eq!(online_column_minima(0, 5, |_, i, j| matrix[i][j]), minima);
677 }
678
679 #[test]
680 fn smawk_works_with_partial_ord() {
681 let matrix = vec![
682 vec![3.0, 2.0], //
683 vec![2.0, 1.0],
684 ];
685 assert_eq!(row_minima(&matrix), vec![1, 1]);
686 assert_eq!(column_minima(&matrix), vec![1, 1]);
687 }
688
689 #[test]
690 fn online_works_with_partial_ord() {
691 let matrix = [
692 [0.0, 2.0], //
693 [0.0, 0.0],
694 ];
695 let minima = vec![(0, 0.0), (0, 2.0)];
696 assert_eq!(online_column_minima(0.0, 2, |_, i, j| matrix[i][j]), minima);
697 }
698}