diff --git a/src/impl_methods.rs b/src/impl_methods.rs index 17cc21b51..2711a98ad 100644 --- a/src/impl_methods.rs +++ b/src/impl_methods.rs @@ -27,6 +27,7 @@ use iterators::{ new_inners_mut, exact_chunks_of, exact_chunks_mut_of, + windows }; use zip::Zip; @@ -46,6 +47,7 @@ use iter::{ AxisIterMut, ExactChunks, ExactChunksMut, + Windows }; use stacking::stack; use PrivateNew; @@ -712,6 +714,18 @@ impl ArrayBase where S: Data, D: Dimension exact_chunks_of(self.view(), chunk_size) } + /// Return a windows iterator. + /// + /// Will iterate over no elements if window size is larger + /// than the actual array size of any dimension. + /// + /// **Panics** if any dimension of `window_size` is zero. + pub fn windows(&self, window_size: E) -> Windows + where E: IntoDimension + { + windows(self.view(), window_size) + } + #[doc(hidden)] #[deprecated(note="Renamed to exact_chunks")] pub fn whole_chunks(&self, chunk_size: E) -> ExactChunks diff --git a/src/iterators/iter.rs b/src/iterators/iter.rs index 2ca30b07b..b008c3d04 100644 --- a/src/iterators/iter.rs +++ b/src/iterators/iter.rs @@ -32,4 +32,5 @@ pub use iterators::{ ExactChunksIter, ExactChunksMut, ExactChunksIterMut, + Windows }; diff --git a/src/iterators/mod.rs b/src/iterators/mod.rs index 917236299..344e69e7e 100644 --- a/src/iterators/mod.rs +++ b/src/iterators/mod.rs @@ -9,6 +9,7 @@ #[macro_use] mod macros; mod chunks; +mod windows; mod lanes; pub mod iter; @@ -30,6 +31,10 @@ use super::{ NdProducer, }; +pub use self::windows::{ + Windows, + windows +}; pub use self::chunks::{ ExactChunks, ExactChunksIter, diff --git a/src/iterators/windows.rs b/src/iterators/windows.rs new file mode 100644 index 000000000..b9897b3eb --- /dev/null +++ b/src/iterators/windows.rs @@ -0,0 +1,55 @@ + +use imp_prelude::*; +use IntoDimension; + +pub struct Windows<'a, A: 'a, D> { + iter : ::iter::Iter<'a, A, D>, + window: D, + stride: D, +} + +pub fn windows(a: ArrayView, window_size: E) -> Windows + where D: Dimension, + E: IntoDimension, +{ + let window = window_size.into_dimension(); + ndassert!(a.ndim() == window.ndim(), + concat!("Window dimension {} does not match array dimension {} ", + "(with array of shape {:?})"), + window.ndim(), a.ndim(), a.shape()); + let mut size = a.raw_dim(); + for (sz, &ws) in size.slice_mut().iter_mut().zip(window.slice()) + { + if ws == 0 { panic!("window-size must not be zero!"); } + // cannot use std::cmp::max(0, ..) since arithmetic underflow panics + *sz = if *sz < ws { 0 } else { *sz - ws + 1 }; + } + + let mut strides = a.raw_dim(); + for (a, b) in strides.slice_mut().iter_mut().zip(a.strides()) { + *a = *b as Ix; + } + + let mult_strides = strides.clone(); + + unsafe { + Windows { + iter : ArrayView::from_shape_ptr(size.clone().strides(mult_strides), a.as_ptr()).into_iter(), + window: window, + stride: strides, + } + } +} + +impl<'a, A, D> Iterator for Windows<'a, A, D> + where D: Dimension, +{ + type Item = ArrayView<'a, A, D>; + fn next(&mut self) -> Option { + self.iter.next().map(|elt| { + unsafe { + ArrayView::from_shape_ptr(self.window.clone().strides(self.stride.clone()), elt) + } + }) + } +} diff --git a/src/lib.rs b/src/lib.rs index a14b1449c..2e83900db 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -135,13 +135,13 @@ mod free_functions; pub use free_functions::*; pub use iterators::iter; +mod si; mod layout; mod indexes; mod iterators; mod linalg_traits; mod linspace; mod numeric_util; -mod si; mod error; mod shape_builder; mod stacking; diff --git a/tests/windows.rs b/tests/windows.rs new file mode 100644 index 000000000..07498143a --- /dev/null +++ b/tests/windows.rs @@ -0,0 +1,99 @@ + +extern crate ndarray; +extern crate itertools; + +use ndarray::prelude::*; + +// Edge Cases for Windows iterator: +// +// - window size is 0 +// - what is the behaviour of the standard for this situation? +// "Panics if size is 0." +// - window size of 1 +// - should a warning be printed? +// - what is the behaviour of the standard for this situation? +// => No overlapping for size-1-windows but normal behaviour besides that. +// - window size bigger than actual array size +// - ragged windows or panic? +// - what is the behaviour of the standard for this situation? +// "If the slice is shorter than size, the iterator returns no values." + +/// Test that verifies the `Windows` iterator panics on window sizes equal to zero. +#[test] +#[should_panic] +fn windows_iterator_zero_size() { + let a = Array::from_iter(10..37) + .into_shape((3, 3, 3)) + .unwrap(); + a.windows(Dim((0, 0, 0))); +} + +/// Test that verifites that no windows are yielded on oversized window sizes. +#[test] +fn windows_iterator_oversized() { + let a = Array::from_iter(10..37) + .into_shape((3, 3, 3)) + .unwrap(); + let mut iter = a.windows(Dim((4, 3, 2))); // (4,3,2) doesn't fit into (3,3,3) => oversized! + assert_eq!(iter.next(), None); +} + +/// Simple test for iterating 1d-arrays via `Windows`. +#[test] +fn windows_iterator_1d() { + let a = Array::from_iter(10..20).into_shape(10).unwrap(); + itertools::assert_equal( + a.windows(Dim(4)), + vec![ + arr1(&[10, 11, 12, 13]), + arr1(&[11, 12, 13, 14]), + arr1(&[12, 13, 14, 15]), + arr1(&[13, 14, 15, 16]), + arr1(&[14, 15, 16, 17]), + arr1(&[15, 16, 17, 18]), + arr1(&[16, 17, 18, 19]) + ]); +} + +/// Simple test for iterating 2d-arrays via `Windows`. +#[test] +fn windows_iterator_2d() { + let a = Array::from_iter(10..30).into_shape((5, 4)).unwrap(); + itertools::assert_equal( + a.windows(Dim((3, 2))), + vec![ + arr2(&[ [10, 11], [14, 15], [18, 19] ]), + arr2(&[ [11, 12], [15, 16], [19, 20] ]), + arr2(&[ [12, 13], [16, 17], [20, 21] ]), + + arr2(&[ [14, 15], [18, 19], [22, 23] ]), + arr2(&[ [15, 16], [19, 20], [23, 24] ]), + arr2(&[ [16, 17], [20, 21], [24, 25] ]), + + arr2(&[ [18, 19], [22, 23], [26, 27] ]), + arr2(&[ [19, 20], [23, 24], [27, 28] ]), + arr2(&[ [20, 21], [24, 25], [28, 29] ]) + ]); +} + +/// Simple test for iterating 3d-arrays via `Windows`. +#[test] +fn windows_iterator_3d() { + use ndarray::arr3; + let a = Array::from_iter(10..37).into_shape((3, 3, 3)).unwrap(); + itertools::assert_equal( + a.windows(Dim((2, 2, 2))), + vec![ + arr3(&[ [[10, 11], [13, 14]], [[19, 20], [22, 23]] ]), + arr3(&[ [[11, 12], [14, 15]], [[20, 21], [23, 24]] ]), + + arr3(&[ [[13, 14], [16, 17]], [[22, 23], [25, 26]] ]), + arr3(&[ [[14, 15], [17, 18]], [[23, 24], [26, 27]] ]), + + arr3(&[ [[19, 20], [22, 23]], [[28, 29], [31, 32]] ]), + arr3(&[ [[20, 21], [23, 24]], [[29, 30], [32, 33]] ]), + + arr3(&[ [[22, 23], [25, 26]], [[31, 32], [34, 35]] ]), + arr3(&[ [[23, 24], [26, 27]], [[32, 33], [35, 36]] ]), + ]); +} \ No newline at end of file