mod idx;
pub use self::idx::Idx;
use std::iter::FusedIterator;
use std::ops::Range;
#[derive(Debug, Clone)]
pub struct Range2D<I: Idx = usize> {
y_range: Range<I>,
x_range: Range<I>,
start: usize,
end: usize,
}
impl<I: Idx> Range2D<I> {
pub fn new(y_range: Range<I>, x_range: Range<I>) -> Self {
let height = y_range.end.saturating_sub(y_range.start).to_usize();
let width = x_range.end.saturating_sub(x_range.start).to_usize();
let total = height * width;
Self {
y_range,
x_range,
start: 0,
end: total,
}
}
pub fn full(height: I, width: I) -> Self {
Self::new(I::zero()..height, I::zero()..width)
}
pub fn reset(&mut self) {
self.start = 0;
self.end = self.total_len();
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn len(&self) -> usize {
self.end.saturating_sub(self.start)
}
pub fn total_len(&self) -> usize {
let height = self.y_range.end.saturating_sub(self.y_range.start);
let width = self.x_range.end.saturating_sub(self.x_range.start);
(height * width).to_usize()
}
pub fn split(&self) -> (Self, Self) {
let mid = self.start + self.len() / 2;
let left = Self {
start: self.start,
end: mid,
..self.clone()
};
let right = Self {
start: mid,
end: self.end,
..self.clone()
};
(left, right)
}
pub fn split_into(&self, n: usize) -> Vec<Self> {
if n == 0 {
return Vec::new();
}
let total = self.len();
let base = total / n;
let rem = total % n;
let mut result = Vec::with_capacity(n);
let mut current_start = self.start;
for i in 0..n {
let chunk_len = base + if i < rem { 1 } else { 0 };
let chunk_end = current_start + chunk_len;
result.push(Self {
start: current_start,
end: chunk_end,
..self.clone()
});
current_start = chunk_end;
}
result
}
pub fn chunks_of(&self, chunk_size: usize) -> Vec<Self> {
if chunk_size == 0 {
return Vec::new();
}
let mut result = Vec::new();
let mut current_start = self.start;
while current_start < self.end {
let chunk_end = (current_start + chunk_size).min(self.end);
result.push(Self {
start: current_start,
end: chunk_end,
..self.clone()
});
current_start = chunk_end;
}
result
}
fn index_to_coord(&self, index: usize) -> (I, I) {
let width = (self.x_range.end - self.x_range.start).to_usize();
if width == 0 {
return (I::zero(), I::zero());
}
let y = self.y_range.start + I::from_usize(index / width.to_usize());
let x = self.x_range.start + I::from_usize(index % width.to_usize());
(y, x)
}
}
impl<I: Idx> Iterator for Range2D<I> {
type Item = (I, I);
fn next(&mut self) -> Option<Self::Item> {
if self.start >= self.end {
return None;
}
let coord = self.index_to_coord(self.start);
self.start += 1;
Some(coord)
}
fn size_hint(&self) -> (usize, Option<usize>) {
let len = self.len();
(len, Some(len))
}
fn nth(&mut self, n: usize) -> Option<Self::Item> {
self.start = self.start.saturating_add(n);
self.next()
}
}
impl<I: Idx> DoubleEndedIterator for Range2D<I> {
fn next_back(&mut self) -> Option<Self::Item> {
if self.start >= self.end {
return None;
}
self.end -= 1;
Some(self.index_to_coord(self.end))
}
}
impl<I: Idx> ExactSizeIterator for Range2D<I> {}
impl<I: Idx> FusedIterator for Range2D<I> {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_full_tile_chunk_bounds() {
let h = 3;
let w = 4;
let coords: Vec<(usize, usize)> = Range2D::full(h, w).collect();
let expected: Vec<(usize, usize)> =
(0..h).flat_map(|y| (0..w).map(move |x| (y, x))).collect();
assert_eq!(coords, expected);
}
#[test]
fn test_custom_ranges() {
let coords: Vec<_> = Range2D::new(2..4, 5..7).collect();
let expected = vec![(2, 5), (2, 6), (3, 5), (3, 6)];
assert_eq!(coords, expected);
}
#[test]
fn test_empty_x_range() {
let coords: Vec<_> = Range2D::new(0..5, 3..3).collect();
assert!(coords.is_empty());
}
#[test]
fn test_empty_y_range() {
let coords: Vec<_> = Range2D::new(2..2, 0..10).collect();
assert!(coords.is_empty());
}
#[test]
fn test_single_element() {
let coords: Vec<_> = Range2D::new(3..4, 7..8).collect();
assert_eq!(coords, vec![(3, 7)]);
}
#[test]
fn test_iter_next_behavior() {
let mut iter = Range2D::new(1..3, 0..2).into_iter();
assert_eq!(iter.next(), Some((1, 0)));
assert_eq!(iter.next(), Some((1, 1)));
assert_eq!(iter.next(), Some((2, 0)));
assert_eq!(iter.next(), Some((2, 1)));
assert_eq!(iter.next(), None);
}
#[test]
fn test_next_back_only() {
let iter = Range2D::new(0..2, 0..3); let coords: Vec<_> = iter.rev().collect();
let expected = vec![(1, 2), (1, 1), (1, 0), (0, 2), (0, 1), (0, 0)];
assert_eq!(coords, expected);
}
#[test]
fn test_next_neg_back_only() {
let iter = Range2D::new(-2..0, -3..0); let coords: Vec<_> = iter.rev().collect();
let expected = vec![(-1, -1), (-1, -2), (-1, -3), (-2, -1), (-2, -2), (-2, -3)];
assert_eq!(coords, expected);
}
#[test]
fn test_mixed_next_and_next_back() {
let mut iter = Range2D::new(0..2, 0..3);
assert_eq!(iter.next(), Some((0, 0))); assert_eq!(iter.next_back(), Some((1, 2))); assert_eq!(iter.next(), Some((0, 1)));
assert_eq!(iter.next_back(), Some((1, 1)));
assert_eq!(iter.next(), Some((0, 2)));
assert_eq!(iter.next_back(), Some((1, 0)));
assert_eq!(iter.next(), None); assert_eq!(iter.next_back(), None);
}
#[test]
fn test_double_ended_len_and_exhaustion() {
let mut iter = Range2D::new(5..6, 10..14);
assert_eq!(iter.len(), 4);
iter.next(); assert_eq!(iter.len(), 3);
iter.next_back(); assert_eq!(iter.len(), 2);
iter.next(); iter.next(); assert_eq!(iter.len(), 0);
assert_eq!(iter.next(), None);
assert_eq!(iter.next_back(), None);
}
#[test]
fn test_split_preserves_order() {
let range = Range2D::new(0..2, 0..4); let (left, right) = range.split();
let all: Vec<_> = left.chain(right).collect();
let expected = vec![
(0, 0),
(0, 1),
(0, 2),
(0, 3),
(1, 0),
(1, 1),
(1, 2),
(1, 3),
];
assert_eq!(all, expected);
}
#[test]
fn test_split_into_chunks() {
let iter = Range2D::new(0..2, 0..4); let chunks = iter.split_into(3);
let sizes: Vec<_> = chunks.iter().map(|c| c.len()).collect();
assert_eq!(sizes, vec![3, 3, 2]);
let all: Vec<_> = chunks.into_iter().flat_map(|c| c).collect();
let expected = vec![
(0, 0),
(0, 1),
(0, 2),
(0, 3),
(1, 0),
(1, 1),
(1, 2),
(1, 3),
];
assert_eq!(all, expected);
}
#[test]
fn test_nth() {
let mut iter = Range2D::new(1..3, 4..8); assert_eq!(iter.nth(0), Some((1, 4)));
assert_eq!(iter.nth(2), Some((1, 7))); assert_eq!(iter.nth(10), None); }
}