Skip to content

Commit ce41a89

Browse files
Merge #308
308: Better Matrix corner cases documentation r=samueltardieu a=samueltardieu Co-authored-by: Samuel Tardieu <[email protected]>
2 parents e45da62 + 05379ec commit ce41a89

File tree

3 files changed

+104
-20
lines changed

3 files changed

+104
-20
lines changed

src/matrix.rs

Lines changed: 56 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ use itertools::Itertools;
66
use num_traits::Signed;
77
use std::error::Error;
88
use std::fmt;
9-
use std::ops::RangeInclusive;
109
use std::ops::{Deref, DerefMut, Index, IndexMut, Neg, Range};
1110
use std::slice::{Iter, IterMut};
1211

@@ -24,7 +23,18 @@ pub struct Matrix<C> {
2423

2524
impl<C: Clone> Matrix<C> {
2625
/// Create new matrix with an initial value.
26+
///
27+
/// # Panics
28+
///
29+
/// This function panics if the number of rows is greater than 0
30+
/// and the number of columns is 0. If you need to build a matrix
31+
/// column by column, build it row by row and call transposition
32+
/// or rotation functions on it.
2733
pub fn new(rows: usize, columns: usize, value: C) -> Self {
34+
assert!(
35+
rows == 0 || columns > 0,
36+
"unable to create a matrix with empty rows"
37+
);
2838
Self {
2939
rows,
3040
columns,
@@ -125,6 +135,10 @@ impl<C: Clone> Matrix<C> {
125135
/// Return a copy of the matrix after transposition.
126136
#[must_use]
127137
pub fn transposed(&self) -> Self {
138+
assert!(
139+
self.rows != 0 || self.columns == 0,
140+
"this operation would create a matrix with empty rows"
141+
);
128142
Self {
129143
rows: self.columns,
130144
columns: self.rows,
@@ -136,8 +150,13 @@ impl<C: Clone> Matrix<C> {
136150

137151
/// Extend the matrix in place by adding one full row. An error
138152
/// is returned if the row does not have the expected number of
139-
/// elements.
153+
/// elements or if an empty row is passed.
140154
pub fn extend(&mut self, row: &[C]) -> Result<(), MatrixFormatError> {
155+
if row.is_empty() {
156+
return Err(MatrixFormatError {
157+
message: "adding an empty row is not allowed".to_owned(),
158+
});
159+
}
141160
if self.columns != row.len() {
142161
return Err(MatrixFormatError {
143162
message: format!(
@@ -193,6 +212,11 @@ impl<C> Matrix<C> {
193212
columns: usize,
194213
values: Vec<C>,
195214
) -> Result<Self, MatrixFormatError> {
215+
if rows != 0 && columns == 0 {
216+
return Err(MatrixFormatError {
217+
message: "creating a matrix with empty rows is not allowed".to_owned(),
218+
});
219+
}
196220
if rows * columns != values.len() {
197221
return Err(MatrixFormatError { message: format!("length of vector does not correspond to announced dimensions ({} instead of {}×{}={})", values.len(), rows, columns, rows*columns)});
198222
}
@@ -230,10 +254,17 @@ impl<C> Matrix<C> {
230254
}
231255
}
232256

257+
/// Check if the matrix is empty.
258+
#[must_use]
259+
pub fn is_empty(&self) -> bool {
260+
self.rows == 0
261+
}
262+
233263
/// Create a matrix from something convertible to an iterator on rows,
234264
/// each row being convertible to an iterator on columns.
235265
///
236-
/// An error will be returned if length of rows differ.
266+
/// An error will be returned if length of rows differ or if empty rows
267+
/// are added.
237268
///
238269
/// ```
239270
/// use pathfinding::matrix::*;
@@ -374,14 +405,25 @@ impl<C> Matrix<C> {
374405
}
375406

376407
/// Return an iterator on neighbours of a given matrix cell, with or
377-
/// without considering diagonals.
408+
/// without considering diagonals. The neighbours list is determined
409+
/// at the time of calling this method and will not change even if new
410+
/// rows are added between the method call and the iterator consumption.
411+
///
412+
/// This function returns an empty iterator if the reference position does
413+
/// not correspond to an existing matrix element.
378414
pub fn neighbours(
379415
&self,
380416
(r, c): (usize, usize),
381417
diagonals: bool,
382418
) -> impl Iterator<Item = (usize, usize)> {
383-
let row_range = RangeInclusive::new(r.saturating_sub(1), (self.rows - 1).min(r + 1));
384-
let col_range = RangeInclusive::new(c.saturating_sub(1), (self.columns - 1).min(c + 1));
419+
let (row_range, col_range) = if r < self.rows && c < self.columns {
420+
(
421+
r.saturating_sub(1)..(self.rows).min(r + 2),
422+
c.saturating_sub(1)..(self.columns).min(c + 2),
423+
)
424+
} else {
425+
(0..0, 0..0)
426+
};
385427
row_range
386428
.cartesian_product(col_range)
387429
.filter(move |&(rr, cc)| (rr != r || cc != c) && (diagonals || rr == r || cc == c))
@@ -455,7 +497,9 @@ impl<C> Matrix<C> {
455497
self.into_iter()
456498
}
457499

458-
/// Return an iterator on the Matrix indices, first row first.
500+
/// Return an iterator on the Matrix indices, first row first. The values are
501+
/// computed when this method is called and will not change even if new rows are
502+
/// added before the iterator is consumed.
459503
pub fn indices(&self) -> impl Iterator<Item = (usize, usize)> {
460504
let columns = self.columns;
461505
(0..self.rows).flat_map(move |r| (0..columns).map(move |c| (r, c)))
@@ -510,7 +554,7 @@ where
510554
IC: IntoIterator<Item = C>,
511555
{
512556
fn from_iter<T: IntoIterator<Item = IC>>(iter: T) -> Self {
513-
Matrix::from_rows(iter).expect("unable to build matrix from irregular iterator")
557+
Matrix::from_rows(iter).unwrap()
514558
}
515559
}
516560

@@ -520,6 +564,7 @@ where
520564
///
521565
/// - `matrix![(row1, row2, …, rowN)]`, each row being an array
522566
/// - `matrix![r1c1, r1c2, …, r1cN; r2c1, …,r2cN; …; rNc1, …, rNcN]`
567+
/// - `matrix![]` creates an empty matrix with a column size of 0
523568
///
524569
/// # Panics
525570
///
@@ -539,7 +584,9 @@ where
539584
/// ```
540585
#[macro_export]
541586
macro_rules! matrix {
542-
() => { compile_error!("a matrix requires at least one row") };
587+
() => {
588+
pathfinding::matrix::Matrix::new_empty(0)
589+
};
543590
($a:expr $(, $b: expr)*$(,)?) => {{
544591
let mut m = pathfinding::matrix::Matrix::new_empty($a.len());
545592
m.extend(&$a).unwrap();

tests/matrix.rs

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,12 @@ fn from_vec_error() {
4040
assert!(Matrix::from_vec(2, 3, vec![20, 30, 40, 50, 60]).is_err());
4141
}
4242

43+
#[test]
44+
#[should_panic]
45+
fn new_empty_row_panic() {
46+
let _ = Matrix::new(1, 0, 42);
47+
}
48+
4349
#[test]
4450
fn to_vec() {
4551
let mut m = Matrix::new(2, 2, 0usize);
@@ -154,6 +160,24 @@ fn non_square_rotate() {
154160
assert_eq!(m0.rotated_ccw(4), m0);
155161
}
156162

163+
#[test]
164+
#[should_panic]
165+
fn no_rows_rotated_cw_panic() {
166+
let _ = Matrix::<u32>::new_empty(10).rotated_cw(1);
167+
}
168+
169+
#[test]
170+
#[should_panic]
171+
fn no_rows_rotated_ccw_panic() {
172+
let _ = Matrix::<u32>::new_empty(10).rotated_ccw(1);
173+
}
174+
175+
#[test]
176+
fn no_rows_rotated_twice() {
177+
let _ = Matrix::<u32>::new_empty(10).rotated_cw(2);
178+
let _ = Matrix::<u32>::new_empty(10).rotated_ccw(2);
179+
}
180+
157181
#[test]
158182
fn flip() {
159183
let m1 = Matrix::square_from_vec(vec![0, 1, 2, 3]).unwrap();
@@ -176,6 +200,12 @@ fn transpose() {
176200
assert_eq!(m2.transposed(), m1);
177201
}
178202

203+
#[test]
204+
#[should_panic]
205+
fn no_rows_transposed_panic() {
206+
let _ = Matrix::<u32>::new_empty(10).transposed();
207+
}
208+
179209
fn sum(slice: &[usize]) -> usize {
180210
slice.iter().sum::<usize>()
181211
}
@@ -262,10 +292,15 @@ fn empty_extend() {
262292
}
263293

264294
#[test]
265-
#[should_panic]
266-
fn extend_bad_size_panic() {
295+
fn extend_bad_size_error() {
267296
let mut m = Matrix::new_empty(3);
268-
m.extend(&[0, 1]).unwrap();
297+
assert!(m.extend(&[0, 1]).is_err());
298+
}
299+
300+
#[test]
301+
fn extend_empty_row_error() {
302+
let mut m: Matrix<u32> = matrix![];
303+
assert!(m.extend(&[]).is_err());
269304
}
270305

271306
#[test]
@@ -334,6 +369,16 @@ fn neighbours() {
334369
}
335370
}
336371

372+
#[test]
373+
fn empty_neighbours() {
374+
let m: Matrix<u32> = matrix![];
375+
assert_eq!(m.neighbours((0, 0), false).collect::<Vec<_>>(), vec![]);
376+
assert_eq!(m.neighbours((0, 0), true).collect::<Vec<_>>(), vec![]);
377+
let m = Matrix::new(10, 10, 42);
378+
assert_eq!(m.neighbours((10, 10), false).collect::<Vec<_>>(), vec![]);
379+
assert_eq!(m.neighbours((10, 10), true).collect::<Vec<_>>(), vec![]);
380+
}
381+
337382
#[test]
338383
fn from_rows() {
339384
let m = Matrix::from_rows((1..3).map(|n| (1..5).map(move |x| x * n))).unwrap();

tests/ui/matrix.stderr

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,3 @@
1-
error: a matrix requires at least one row
2-
--> tests/ui/matrix.rs:5:13
3-
|
4-
5 | let _ = matrix!();
5-
| ^^^^^^^^^
6-
|
7-
= note: this error originates in the macro `matrix` (in Nightly builds, run with -Z macro-backtrace for more info)
8-
91
error: no rules expected the token `,`
102
--> tests/ui/matrix.rs:7:21
113
|

0 commit comments

Comments
 (0)