Repository: bluss/rust-ndarray
Pull request #306 (2017-04-06 21:21:44)

Added basic implementation of windows iterators
**Features:**
- `Windows` struct based on `BaseIter`
- free function `fn windows` used internally, similar to `fn exact_chunks`
- added to ndarray trait `fn windows(&self, ...)` for `a.windows(Dim(...))` usage
- also include some tests that also test for edge cases
- maybe some useful docs are still missing (no time, yet)

Addresses issue #276.


Patch:
diff --git a/src/impl_methods.rs b/src/impl_methods.rs
index 17cc21b5..2711a98a 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<A, S, D> ArrayBase<S, D> where S: Data<Elem=A>, 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<E>(&self, window_size: E) -> Windows<A, D>
+        where E: IntoDimension<Dim=D>
+    {
+        windows(self.view(), window_size)
+    }
+
     #[doc(hidden)]
     #[deprecated(note="Renamed to exact_chunks")]
     pub fn whole_chunks<E>(&self, chunk_size: E) -> ExactChunks<A, D> 
diff --git a/src/iterators/iter.rs b/src/iterators/iter.rs
index 2ca30b07..b008c3d0 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 91723629..344e69e7 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 00000000..b9897b3e
--- /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, D, E>(a: ArrayView<A, D>, window_size: E) -> Windows<A, D>
+	where D: Dimension,
+          E: IntoDimension<Dim=D>,
+{
+    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::Item> {
+        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 a14b1449..2e83900d 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;


Test patch:
diff --git a/tests/windows.rs b/tests/windows.rs
new file mode 100644
index 00000000..07498143
--- /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