#![no_std]
#![doc(html_root_url = "https://docs.rs/smawk/0.3.3")]
#![cfg_attr(not(feature = "ndarray"), forbid(unsafe_code))]
extern crate alloc;
use alloc::vec;
use alloc::vec::Vec;
#[cfg(feature = "ndarray")]
pub mod brute_force;
pub mod monge;
#[cfg(feature = "ndarray")]
pub mod recursive;
pub trait Matrix<T: Copy> {
fn nrows(&self) -> usize;
fn ncols(&self) -> usize;
fn index(&self, row: usize, column: usize) -> T;
}
impl<T: Copy> Matrix<T> for Vec<Vec<T>> {
fn nrows(&self) -> usize {
self.len()
}
fn ncols(&self) -> usize {
self[0].len()
}
fn index(&self, row: usize, column: usize) -> T {
self[row][column]
}
}
#[cfg(feature = "ndarray")]
impl<T: Copy> Matrix<T> for ndarray::Array2<T> {
#[inline]
fn nrows(&self) -> usize {
self.nrows()
}
#[inline]
fn ncols(&self) -> usize {
self.ncols()
}
#[inline]
fn index(&self, row: usize, column: usize) -> T {
self[[row, column]]
}
}
pub fn row_minima<T: PartialOrd + Copy, M: Matrix<T>>(matrix: &M) -> Vec<usize> {
let mut minima = vec![0; matrix.nrows()];
let (mut rows_scratch, mut cols_scratch) = scratchpads(matrix.ncols(), matrix.nrows());
smawk_inner(
&|j, i| matrix.index(i, j),
(&mut rows_scratch, 0, matrix.ncols()),
(&mut cols_scratch, 0, matrix.nrows()),
&mut minima,
);
minima
}
#[deprecated(since = "0.3.2", note = "Please use `row_minima` instead.")]
pub fn smawk_row_minima<T: PartialOrd + Copy, M: Matrix<T>>(matrix: &M) -> Vec<usize> {
row_minima(matrix)
}
pub fn column_minima<T: PartialOrd + Copy, M: Matrix<T>>(matrix: &M) -> Vec<usize> {
let mut minima = vec![0; matrix.ncols()];
let (mut rows_scratch, mut cols_scratch) = scratchpads(matrix.nrows(), matrix.ncols());
smawk_inner(
&|i, j| matrix.index(i, j),
(&mut rows_scratch, 0, matrix.nrows()),
(&mut cols_scratch, 0, matrix.ncols()),
&mut minima,
);
minima
}
#[deprecated(since = "0.3.2", note = "Please use `column_minima` instead.")]
pub fn smawk_column_minima<T: PartialOrd + Copy, M: Matrix<T>>(matrix: &M) -> Vec<usize> {
column_minima(matrix)
}
#[inline(always)]
fn scratchpads_empty(nrows: usize, ncols: usize) -> (Vec<usize>, Vec<usize>) {
let rows_scratch = vec![0; nrows + 2 * ncols];
let cols_scratch = vec![0; ncols * 2];
(rows_scratch, cols_scratch)
}
#[inline(always)]
fn scratchpads(nrows: usize, ncols: usize) -> (Vec<usize>, Vec<usize>) {
let (mut rows_scratch, mut cols_scratch) = scratchpads_empty(nrows, ncols);
for (i, val) in rows_scratch.iter_mut().enumerate().take(nrows) {
*val = i;
}
for (i, val) in cols_scratch.iter_mut().enumerate().take(ncols) {
*val = i;
}
(rows_scratch, cols_scratch)
}
fn smawk_inner<T: PartialOrd + Copy, M: Fn(usize, usize) -> T>(
matrix: &M,
(rows_scratch, rows_start, rows_end): (&mut [usize], usize, usize),
(cols_scratch, cols_start, cols_end): (&mut [usize], usize, usize),
minima: &mut [usize],
) {
if cols_start == cols_end {
return;
}
let cols_len = cols_end - cols_start;
let stack_start = rows_end;
let odd_cols_start = cols_end;
let odd_cols_len = cols_len / 2;
assert!(rows_end <= rows_scratch.len());
assert!(stack_start + cols_len <= rows_scratch.len());
assert!(cols_start + 2 * odd_cols_len <= cols_scratch.len());
assert!(odd_cols_start + odd_cols_len <= cols_scratch.len());
let mut stack_len = 0;
for i in rows_start..rows_end {
let r = rows_scratch[i];
while stack_len > 0 {
let stack_top = rows_scratch[stack_start + stack_len - 1];
let col = cols_scratch[cols_start + stack_len - 1];
if matrix(stack_top, col) > matrix(r, col) {
stack_len -= 1;
} else {
break;
}
}
if stack_len < cols_len {
rows_scratch[stack_start + stack_len] = r;
stack_len += 1;
}
}
for idx in 0..odd_cols_len {
let col = cols_scratch[cols_start + 2 * idx + 1];
cols_scratch[odd_cols_start + idx] = col;
}
smawk_inner(
matrix,
(&mut *rows_scratch, stack_start, stack_start + stack_len),
(
&mut *cols_scratch,
odd_cols_start,
odd_cols_start + odd_cols_len,
),
minima,
);
let mut r = 0;
assert!(stack_start + stack_len <= rows_scratch.len());
for c in 0..cols_len {
if c % 2 == 0 {
let col = cols_scratch[cols_start + c];
let mut row = rows_scratch[stack_start + r];
let last_row = if c == cols_len - 1 {
rows_scratch[stack_start + stack_len - 1]
} else {
minima[cols_scratch[cols_start + c + 1]]
};
let mut pair = (matrix(row, col), row);
while row != last_row && r < stack_len - 1 {
r += 1;
row = rows_scratch[stack_start + r];
if (matrix(row, col), row) < pair {
pair = (matrix(row, col), row);
}
}
minima[col] = pair.1;
}
}
}
pub fn online_column_minima<T: Copy + PartialOrd, M: Fn(&[(usize, T)], usize, usize) -> T>(
initial: T,
size: usize,
matrix: M,
) -> Vec<(usize, T)> {
let mut result = Vec::with_capacity(size);
result.push((0, initial));
let mut finished = 0;
let mut base = 0;
let mut tentative = 0;
macro_rules! m {
($i:expr, $j:expr) => {{
matrix(&result[..finished + 1], $i, $j)
}};
}
let (mut rows_scratch, mut cols_scratch) = scratchpads_empty(size, size);
let mut minima = vec![0; size];
while finished < size - 1 {
let i = finished + 1;
if i > tentative {
let rows_start = 0;
let rows_end = finished + 1 - base;
for (idx, r) in (base..finished + 1).enumerate() {
rows_scratch[idx] = r;
}
tentative = core::cmp::min(finished + rows_end, size - 1);
let cols_start = 0;
let cols_end = tentative - finished;
for (idx, c) in (finished + 1..tentative + 1).enumerate() {
cols_scratch[idx] = c;
}
smawk_inner(
&|i, j| m![i, j],
(&mut rows_scratch, rows_start, rows_end),
(&mut cols_scratch, cols_start, cols_end),
&mut minima,
);
for col in finished + 1..tentative + 1 {
let row = minima[col];
let v = m![row, col];
if col >= result.len() {
result.push((row, v));
} else if v < result[col].1 {
result[col] = (row, v);
}
}
finished = i;
continue;
}
let diag = m![i - 1, i];
if diag < result[i].1 {
result[i] = (i - 1, diag);
base = i - 1;
tentative = i;
finished = i;
continue;
}
if m![i - 1, tentative] >= result[tentative].1 {
finished = i;
continue;
}
base = i - 1;
tentative = i;
finished = i;
}
result
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn smawk_1x1() {
let matrix = vec![vec![2]];
assert_eq!(row_minima(&matrix), vec![0]);
assert_eq!(column_minima(&matrix), vec![0]);
}
#[test]
fn smawk_2x1() {
let matrix = vec![
vec![3], vec![2],
];
assert_eq!(row_minima(&matrix), vec![0, 0]);
assert_eq!(column_minima(&matrix), vec![1]);
}
#[test]
fn smawk_1x2() {
let matrix = vec![vec![2, 1]];
assert_eq!(row_minima(&matrix), vec![1]);
assert_eq!(column_minima(&matrix), vec![0, 0]);
}
#[test]
fn smawk_2x2() {
let matrix = vec![
vec![3, 2], vec![2, 1],
];
assert_eq!(row_minima(&matrix), vec![1, 1]);
assert_eq!(column_minima(&matrix), vec![1, 1]);
}
#[test]
fn smawk_3x3() {
let matrix = vec![
vec![3, 4, 4], vec![3, 4, 4],
vec![2, 3, 3],
];
assert_eq!(row_minima(&matrix), vec![0, 0, 0]);
assert_eq!(column_minima(&matrix), vec![2, 2, 2]);
}
#[test]
fn smawk_4x4() {
let matrix = vec![
vec![4, 5, 5, 5], vec![2, 3, 3, 3],
vec![2, 3, 3, 3],
vec![2, 2, 2, 2],
];
assert_eq!(row_minima(&matrix), vec![0, 0, 0, 0]);
assert_eq!(column_minima(&matrix), vec![1, 3, 3, 3]);
}
#[test]
fn smawk_5x5() {
let matrix = vec![
vec![3, 2, 4, 5, 6],
vec![2, 1, 3, 3, 4],
vec![2, 1, 3, 3, 4],
vec![3, 2, 4, 3, 4],
vec![4, 3, 2, 1, 1],
];
assert_eq!(row_minima(&matrix), vec![1, 1, 1, 1, 3]);
assert_eq!(column_minima(&matrix), vec![1, 1, 4, 4, 4]);
}
#[test]
fn online_1x1() {
let matrix = [[0]];
let minima = vec![(0, 0)];
assert_eq!(online_column_minima(0, 1, |_, i, j| matrix[i][j]), minima);
}
#[test]
fn online_2x2() {
let matrix = [
[0, 2], [0, 0],
];
let minima = vec![(0, 0), (0, 2)];
assert_eq!(online_column_minima(0, 2, |_, i, j| matrix[i][j]), minima);
}
#[test]
fn online_3x3() {
let matrix = [
[0, 4, 4], [0, 0, 4],
[0, 0, 0],
];
let minima = vec![(0, 0), (0, 4), (0, 4)];
assert_eq!(online_column_minima(0, 3, |_, i, j| matrix[i][j]), minima);
}
#[test]
fn online_4x4() {
let matrix = [
[0, 5, 5, 5], [0, 0, 3, 3],
[0, 0, 0, 3],
[0, 0, 0, 0],
];
let minima = vec![(0, 0), (0, 5), (1, 3), (1, 3)];
assert_eq!(online_column_minima(0, 4, |_, i, j| matrix[i][j]), minima);
}
#[test]
fn online_5x5() {
let matrix = [
[0, 2, 4, 6, 7],
[0, 0, 3, 4, 5],
[0, 0, 0, 3, 4],
[0, 0, 0, 0, 4],
[0, 0, 0, 0, 0],
];
let minima = vec![(0, 0), (0, 2), (1, 3), (2, 3), (2, 4)];
assert_eq!(online_column_minima(0, 5, |_, i, j| matrix[i][j]), minima);
}
#[test]
fn smawk_works_with_partial_ord() {
let matrix = vec![
vec![3.0, 2.0], vec![2.0, 1.0],
];
assert_eq!(row_minima(&matrix), vec![1, 1]);
assert_eq!(column_minima(&matrix), vec![1, 1]);
}
#[test]
fn online_works_with_partial_ord() {
let matrix = [
[0.0, 2.0], [0.0, 0.0],
];
let minima = vec![(0, 0.0), (0, 2.0)];
assert_eq!(online_column_minima(0.0, 2, |_, i, j| matrix[i][j]), minima);
}
}