use crate::application::view::ArrayView;
use crate::domain::layout::Layout;
pub trait LendingIterator {
type Item<'this>
where
Self: 'this;
fn next(&mut self) -> Option<Self::Item<'_>>;
#[inline]
fn count_remaining(&mut self) -> usize {
let mut n = 0;
while self.next().is_some() {
n += 1;
}
n
}
}
pub struct Tiles<'a, T, const N: usize> {
data: &'a [T],
parent_layout: Layout<N>,
tile_shape: [usize; N],
tile_grid: [usize; N],
total: usize,
cursor: usize,
}
impl<'a, T, const N: usize> Tiles<'a, T, N> {
#[must_use]
pub fn new(data: &'a [T], parent_layout: Layout<N>, tile_shape: [usize; N]) -> Option<Self> {
if N == 0 {
return None;
}
if tile_shape.contains(&0) {
return None;
}
let parent_shape = parent_layout.shape;
let mut tile_grid = [0usize; N];
let mut total = 1usize;
for i in 0..N {
tile_grid[i] = parent_shape[i].div_ceil(tile_shape[i]);
total = total.checked_mul(tile_grid[i])?;
}
Some(Self {
data,
parent_layout,
tile_shape,
tile_grid,
total,
cursor: 0,
})
}
#[must_use]
#[inline]
pub fn total_tiles(&self) -> usize {
self.total
}
fn flat_to_tile_index(&self, flat: usize) -> [usize; N] {
let mut idx = [0usize; N];
let mut rem = flat;
for i in (0..N).rev() {
idx[i] = rem % self.tile_grid[i];
rem /= self.tile_grid[i];
}
idx
}
fn tile_extent(&self, tile_idx: &[usize; N]) -> [usize; N] {
let parent_shape = self.parent_layout.shape;
let mut extent = [0usize; N];
for i in 0..N {
let start = tile_idx[i] * self.tile_shape[i];
let end = (start + self.tile_shape[i]).min(parent_shape[i]);
extent[i] = end - start;
}
extent
}
}
impl<'a, T: Copy, const N: usize> LendingIterator for Tiles<'a, T, N> {
type Item<'this>
= ArrayView<'this, T, N>
where
Self: 'this;
fn next(&mut self) -> Option<Self::Item<'_>> {
if self.cursor >= self.total {
return None;
}
let tile_idx = self.flat_to_tile_index(self.cursor);
let extent = self.tile_extent(&tile_idx);
let origin: [usize; N] = std::array::from_fn(|i| tile_idx[i] * self.tile_shape[i]);
let offset = self.parent_layout.offset_of(origin).ok()?;
let view_layout = Layout::new(extent, self.parent_layout.strides, offset);
self.cursor += 1;
Some(ArrayView::new(view_layout, self.data))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::application::array::Array;
use crate::infrastructure::storage::{Storage, VecStorage};
fn array2x3() -> Array<f64, VecStorage<f64>, 2> {
Array::from_shape_vec([2, 3], vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0])
.expect("shape matches data")
}
#[test]
fn tile_count_exact_divisible() {
let a = array2x3();
let tiles =
Tiles::new(a.storage().as_slice(), a.layout(), [1, 3]).expect("valid tile shape");
assert_eq!(tiles.total_tiles(), 2);
}
#[test]
fn tile_count_partial_boundary() {
let a = array2x3();
let tiles =
Tiles::new(a.storage().as_slice(), a.layout(), [1, 2]).expect("valid tile shape");
assert_eq!(tiles.total_tiles(), 4);
}
#[test]
fn tiles_cover_all_elements() {
let a = array2x3();
let mut tiles =
Tiles::new(a.storage().as_slice(), a.layout(), [1, 2]).expect("valid tile shape");
let mut collected = Vec::new();
while let Some(tile) = tiles.next() {
for val in tile.iter() {
collected.push(*val);
}
}
let mut sorted = collected.clone();
sorted.sort_by(|a, b| a.partial_cmp(b).unwrap());
assert_eq!(sorted, vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0]);
}
#[test]
fn tile_yields_correct_values_exact() {
let a = array2x3();
let mut tiles =
Tiles::new(a.storage().as_slice(), a.layout(), [1, 3]).expect("valid tile shape");
let row0 = tiles.next().expect("first tile");
assert_eq!(
row0.iter().copied().collect::<Vec<_>>(),
vec![0.0, 1.0, 2.0]
);
let row1 = tiles.next().expect("second tile");
assert_eq!(
row1.iter().copied().collect::<Vec<_>>(),
vec![3.0, 4.0, 5.0]
);
assert!(tiles.next().is_none());
}
#[test]
fn lending_iterator_manual_loop() {
let a = array2x3();
let mut tiles =
Tiles::new(a.storage().as_slice(), a.layout(), [1, 3]).expect("valid tile shape");
let mut sums = Vec::new();
while let Some(tile) = tiles.next() {
sums.push(tile.iter().copied().sum::<f64>());
}
assert_eq!(sums, vec![3.0, 12.0]); }
#[test]
fn reject_zero_tile_shape() {
let a = array2x3();
assert!(Tiles::new(a.storage().as_slice(), a.layout(), [0, 3]).is_none());
}
#[test]
fn count_remaining_exhausts_iterator() {
let a = array2x3();
let mut tiles =
Tiles::new(a.storage().as_slice(), a.layout(), [1, 3]).expect("valid tile shape");
assert_eq!(tiles.count_remaining(), 2);
assert!(tiles.next().is_none());
}
}