added tests and benchmarks
This commit is contained in:
parent
893849870c
commit
ed78c4e210
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -3,5 +3,5 @@
|
|||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "ndrange"
|
||||
name = "nrange"
|
||||
version = "0.1.0"
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
[package]
|
||||
name = "ndrange"
|
||||
name = "nrange"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[profile.bench]
|
||||
opt-level = 3
|
||||
|
||||
[dependencies]
|
||||
|
|
1
src/bounds_ext.rs
Normal file
1
src/bounds_ext.rs
Normal file
|
@ -0,0 +1 @@
|
|||
use std::ops::RangeBounds;
|
|
@ -3,22 +3,22 @@ use std::{
|
|||
ops::RangeBounds,
|
||||
};
|
||||
|
||||
use crate::{get_bound_start, ndrange::NdRange};
|
||||
use crate::{get_real_bound, range::NRange};
|
||||
|
||||
pub enum NdIter<R, T: Step, const N: usize>
|
||||
pub enum NRangeIter<R, T: Step, const N: usize>
|
||||
where
|
||||
R: RangeBounds<T>,
|
||||
R: ExactSizeIterator,
|
||||
Self: Sized,
|
||||
{
|
||||
Iterating {
|
||||
ndrange: NdRange<R, T, N>,
|
||||
nrange: NRange<R, T, N>,
|
||||
current: [T; N],
|
||||
},
|
||||
Done,
|
||||
}
|
||||
|
||||
impl<R, T, const N: usize> Iterator for NdIter<R, T, N>
|
||||
impl<R, T, const N: usize> Iterator for NRangeIter<R, T, N>
|
||||
where
|
||||
R: RangeBounds<T>,
|
||||
R: ExactSizeIterator,
|
||||
|
@ -28,16 +28,19 @@ where
|
|||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match self {
|
||||
NdIter::Iterating { ndrange, current } => {
|
||||
NRangeIter::Iterating {
|
||||
nrange: ndrange,
|
||||
current,
|
||||
} => {
|
||||
let ret = current.clone();
|
||||
for i in 0..current.len() {
|
||||
let next = T::forward(current[i].clone(), 1);
|
||||
if ndrange.ranges[i].contains(&next) {
|
||||
if ndrange.bounds[i].contains(&next) {
|
||||
current[i] = next.clone();
|
||||
break;
|
||||
} else {
|
||||
current[i] = get_bound_start(&ndrange.ranges[i]);
|
||||
if i == (current.len() - 1) {
|
||||
current[i] = get_real_bound(ndrange.bounds[i].start_bound());
|
||||
if i == (N - 1) {
|
||||
*self = Self::Done;
|
||||
break;
|
||||
}
|
||||
|
@ -45,25 +48,27 @@ where
|
|||
}
|
||||
Some(ret)
|
||||
},
|
||||
NdIter::Done => None,
|
||||
NRangeIter::Done => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
let mut size = 1;
|
||||
match self {
|
||||
NdIter::Iterating { ndrange, .. } => {
|
||||
for i in &ndrange.ranges {
|
||||
NRangeIter::Iterating {
|
||||
nrange: ndrange, ..
|
||||
} => {
|
||||
for i in &ndrange.bounds {
|
||||
size *= i.len();
|
||||
}
|
||||
(size, Some(size))
|
||||
},
|
||||
NdIter::Done => (0, None),
|
||||
NRangeIter::Done => (0, None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<R, T, const N: usize> ExactSizeIterator for NdIter<R, T, N>
|
||||
impl<R, T, const N: usize> ExactSizeIterator for NRangeIter<R, T, N>
|
||||
where
|
||||
R: RangeBounds<T>,
|
||||
R: ExactSizeIterator,
|
||||
|
@ -74,7 +79,7 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<R, T, const N: usize> FusedIterator for NdIter<R, T, N>
|
||||
impl<R, T, const N: usize> FusedIterator for NRangeIter<R, T, N>
|
||||
where
|
||||
R: RangeBounds<T>,
|
||||
R: ExactSizeIterator,
|
51
src/lib.rs
51
src/lib.rs
|
@ -1,5 +1,50 @@
|
|||
#![feature(test)]
|
||||
#![feature(step_trait)]
|
||||
pub mod nditer;
|
||||
pub mod ndrange;
|
||||
|
||||
pub use ndrange::*;
|
||||
//! The `nrange` crate provides an abstraction of the
|
||||
//! standard range types over *n*-dimensions
|
||||
//! While the traditional range types cover a set of linear
|
||||
//! values, the [`NRange`](crate::range::NRange) type
|
||||
//! cover a region in vector space. `NRange` can represent
|
||||
//! the points in a 2D rectangle, 3D cube, 4D hypercube, or
|
||||
//! extend to any number of dimensions using rust's const
|
||||
//! generics.
|
||||
//! # Getting started
|
||||
//! The preferred way of initializing `NRange` is using
|
||||
//! the provided macro. The syntax is similar to the `vec!`
|
||||
//! macro:
|
||||
//! ```
|
||||
//! use nrange::*;
|
||||
//! let range_a = nrange!(0..3, 0..3);
|
||||
//! let range_b = nrange!(0..3; 2);
|
||||
//! assert_eq!(range_a, range_b);
|
||||
//!
|
||||
//! assert!(range_a.contains(&[0, 2]));
|
||||
//!
|
||||
//! for v in range_a {
|
||||
//! print!("{v:?}, "); // [0, 0], [1, 0], [2, 0], [0, 1], ...
|
||||
//! }
|
||||
//! ```
|
||||
|
||||
pub(crate) fn get_real_bound<T>(bound: Bound<&T>) -> T
|
||||
where
|
||||
T: Step,
|
||||
T: Clone,
|
||||
{
|
||||
match bound {
|
||||
Bound::Included(s) => s.clone(),
|
||||
Bound::Excluded(s) => Step::forward(s.clone(), 1),
|
||||
Bound::Unbounded => panic!("Cannot get real bound"),
|
||||
}
|
||||
}
|
||||
|
||||
pub mod bounds_ext;
|
||||
pub mod iter;
|
||||
pub mod range;
|
||||
|
||||
use std::{iter::Step, ops::Bound};
|
||||
|
||||
pub use range::*;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
|
186
src/ndrange.rs
186
src/ndrange.rs
|
@ -1,186 +0,0 @@
|
|||
use std::{iter::Step, marker::PhantomData, ops::RangeBounds};
|
||||
|
||||
use crate::nditer::NdIter;
|
||||
|
||||
pub(crate) fn get_bound_start<R, T>(s: &R) -> T
|
||||
where
|
||||
R: RangeBounds<T>,
|
||||
R: ExactSizeIterator,
|
||||
T: Step,
|
||||
T: Clone,
|
||||
{
|
||||
match s.start_bound() {
|
||||
std::ops::Bound::Included(s) => s.clone(),
|
||||
std::ops::Bound::Excluded(s) => Step::forward(s.clone(), 1),
|
||||
std::ops::Bound::Unbounded => unreachable!(
|
||||
"Implementing exact size iterator means the range cannot be unbounded \
|
||||
at the start"
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
/// A N-Dimensional extension to Range and its derivatives
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// assert_eq!(ndrange!(0..5, 0..5), NdRange::new([0..5, 0..5]));
|
||||
/// assert_eq!(ndrange!(0..3, 0..3).len(), 9);
|
||||
///
|
||||
/// let mut iter = ndrange!(0..2, 0..2).into_iter();
|
||||
/// assert_eq!(iter.next(), Some([0, 0]));
|
||||
/// assert_eq!(iter.next(), Some([1, 0]));
|
||||
/// assert_eq!(iter.next(), Some([0, 1]));
|
||||
/// assert_eq!(iter.next(), Some([1, 1]));
|
||||
/// assert_eq!(iter.next(), None);
|
||||
///
|
||||
/// assert!(ndrange!(0..10, 0..10).contains(&[2, 4]));
|
||||
///
|
||||
/// assert_eq!(ndrange!().len(), 0);
|
||||
/// assert_eq!(ndrange!(0..7, 0..0).len(), 0);
|
||||
/// assert!(!ndrange!(0..3, 0..2, 0..0).contains(&[2, 1, 0]));
|
||||
///
|
||||
/// for i in NdRange::new([0..5, 0..5]) {
|
||||
/// println!("{:?}", i);
|
||||
/// }
|
||||
///
|
||||
/// for _ in ndrange!() {
|
||||
/// panic!("Unreachable, empty NdRange returns an empty
|
||||
/// iterator") }
|
||||
///
|
||||
/// for i in ndrange!(0..5, 0..5) {
|
||||
/// println!("{i:?}");
|
||||
/// }
|
||||
/// ```
|
||||
#[derive(Clone, Eq, Hash)]
|
||||
pub struct NdRange<R, Idx, const N: usize>
|
||||
where
|
||||
R: RangeBounds<Idx>,
|
||||
{
|
||||
pub(crate) ranges: [R; N],
|
||||
pub(crate) _phantom: PhantomData<Idx>,
|
||||
}
|
||||
|
||||
impl<R, Idx, const N: usize> std::fmt::Debug for NdRange<R, Idx, N>
|
||||
where
|
||||
R: RangeBounds<Idx>,
|
||||
R: std::fmt::Debug,
|
||||
{
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "[ ")?;
|
||||
for r in &self.ranges {
|
||||
r.fmt(f)?;
|
||||
write!(f, ", ")?;
|
||||
}
|
||||
write!(f, "]")?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<R, Idx, const N: usize> PartialEq for NdRange<R, Idx, N>
|
||||
where
|
||||
R: RangeBounds<Idx>,
|
||||
R: PartialEq<R>,
|
||||
{
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
for i in 0..N {
|
||||
if self.ranges[i] != other.ranges[i] {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
impl<R, Idx, const N: usize> NdRange<R, Idx, N>
|
||||
where
|
||||
R: RangeBounds<Idx>,
|
||||
{
|
||||
pub fn new(ranges: [R; N]) -> Self {
|
||||
Self {
|
||||
ranges,
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<R, Idx, const N: usize> NdRange<R, Idx, N>
|
||||
where
|
||||
R: RangeBounds<Idx>,
|
||||
Idx: PartialOrd<Idx>,
|
||||
{
|
||||
pub fn contains<U>(&self, other: &[U; N]) -> bool
|
||||
where
|
||||
Idx: PartialOrd<U>,
|
||||
U: PartialOrd<Idx>,
|
||||
{
|
||||
for i in 0..N {
|
||||
if !self.ranges[i].contains(&other[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
impl<R, Idx, const N: usize> NdRange<R, Idx, N>
|
||||
where
|
||||
Self: Sized,
|
||||
R: RangeBounds<Idx>,
|
||||
R: ExactSizeIterator,
|
||||
R: Clone,
|
||||
Idx: Step,
|
||||
{
|
||||
pub fn len(&self) -> usize {
|
||||
self.clone().into_iter().len()
|
||||
}
|
||||
}
|
||||
|
||||
impl<R, Idx, const N: usize> IntoIterator for NdRange<R, Idx, N>
|
||||
where
|
||||
R: RangeBounds<Idx>,
|
||||
R: ExactSizeIterator,
|
||||
Idx: Step,
|
||||
{
|
||||
type IntoIter = NdIter<R, Idx, N>;
|
||||
type Item = [Idx; N];
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
let mut empty = N == 0;
|
||||
for i in 0..N {
|
||||
if self.ranges[i].len() == 0 {
|
||||
empty = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
match empty {
|
||||
true => NdIter::Done,
|
||||
false => NdIter::Iterating {
|
||||
current: std::array::from_fn(|i| get_bound_start(&self.ranges[i])),
|
||||
ndrange: self,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<R, Idx, const N: usize> Default for NdRange<R, Idx, N>
|
||||
where
|
||||
R: RangeBounds<Idx>,
|
||||
R: Default,
|
||||
{
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
ranges: std::array::from_fn(|_| R::default()),
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! ndrange {
|
||||
|
||||
( $( $x:expr ), + $(,)? ) => {
|
||||
NdRange::new([ $($x,)* ])
|
||||
};
|
||||
|
||||
}
|
147
src/range.rs
Normal file
147
src/range.rs
Normal file
|
@ -0,0 +1,147 @@
|
|||
use std::{iter::Step, marker::PhantomData, ops::RangeBounds};
|
||||
|
||||
use crate::{get_real_bound, iter::NRangeIter};
|
||||
|
||||
/// N-dimensional range
|
||||
/// R: any range type implementing RangeBounds<Idx>
|
||||
/// Idx: range bound type (see [`std::ops::Range`])
|
||||
/// N: dimensionality of the range (the *N* in `NRange`)
|
||||
#[derive(Clone, Eq, Hash)]
|
||||
pub struct NRange<R, Idx, const N: usize>
|
||||
where
|
||||
R: RangeBounds<Idx>,
|
||||
{
|
||||
pub bounds: [R; N],
|
||||
pub(crate) _phantom: PhantomData<Idx>,
|
||||
}
|
||||
|
||||
impl<R, Idx, const N: usize> NRange<R, Idx, N>
|
||||
where
|
||||
R: RangeBounds<Idx>,
|
||||
{
|
||||
pub fn new(ranges: [R; N]) -> Self {
|
||||
Self {
|
||||
bounds: ranges,
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<R, Idx, const N: usize> std::fmt::Debug for NRange<R, Idx, N>
|
||||
where
|
||||
R: RangeBounds<Idx>,
|
||||
R: std::fmt::Debug,
|
||||
{
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.bounds.fmt(f)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<R, Idx, const N: usize> PartialEq for NRange<R, Idx, N>
|
||||
where
|
||||
R: RangeBounds<Idx>,
|
||||
R: PartialEq<R>,
|
||||
{
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
for i in 0..N {
|
||||
if self.bounds[i] != other.bounds[i] {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
impl<R, Idx, const N: usize> NRange<R, Idx, N>
|
||||
where
|
||||
R: RangeBounds<Idx>,
|
||||
Idx: PartialOrd<Idx>,
|
||||
{
|
||||
pub fn contains<U>(&self, other: &[U; N]) -> bool
|
||||
where
|
||||
Idx: PartialOrd<U>,
|
||||
U: PartialOrd<Idx>,
|
||||
{
|
||||
for i in 0..N {
|
||||
if !self.bounds[i].contains(&other[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
impl<R, Idx, const N: usize> NRange<R, Idx, N>
|
||||
where
|
||||
Self: Sized,
|
||||
R: RangeBounds<Idx>,
|
||||
R: ExactSizeIterator,
|
||||
R: Clone,
|
||||
Idx: Step,
|
||||
{
|
||||
pub fn len(&self) -> usize {
|
||||
self.clone().into_iter().len()
|
||||
}
|
||||
}
|
||||
|
||||
impl<R, Idx, const N: usize> IntoIterator for NRange<R, Idx, N>
|
||||
where
|
||||
R: RangeBounds<Idx>,
|
||||
R: ExactSizeIterator,
|
||||
Idx: Step,
|
||||
{
|
||||
type IntoIter = NRangeIter<R, Idx, N>;
|
||||
type Item = [Idx; N];
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
let mut empty = N == 0;
|
||||
for i in 0..N {
|
||||
if self.bounds[i].len() == 0 {
|
||||
empty = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
match empty {
|
||||
true => NRangeIter::Done,
|
||||
false => NRangeIter::Iterating {
|
||||
current: std::array::from_fn(|i| {
|
||||
get_real_bound(self.bounds[i].start_bound())
|
||||
}),
|
||||
nrange: self,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<R, Idx, const N: usize> Default for NRange<R, Idx, N>
|
||||
where
|
||||
R: RangeBounds<Idx>,
|
||||
R: Default,
|
||||
{
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
bounds: std::array::from_fn(|_| R::default()),
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
#[macro_use]
|
||||
pub mod create_macro {
|
||||
#[macro_export]
|
||||
macro_rules! nrange {
|
||||
() => {compile_error!("NRange cannot have 0 dimensions!")};
|
||||
|
||||
($( $x:expr ), + $(,)?) => {
|
||||
$crate::range::NRange::new([ $($x,)* ])
|
||||
};
|
||||
|
||||
($elem:expr; $n:expr) => (
|
||||
{
|
||||
let arr: [_; $n] = std::array::from_fn(|_| {$elem});
|
||||
$crate::range::NRange::new(arr)
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
61
src/tests.rs
Normal file
61
src/tests.rs
Normal file
|
@ -0,0 +1,61 @@
|
|||
use super::*;
|
||||
extern crate test;
|
||||
use std::hint::black_box;
|
||||
use test::Bencher;
|
||||
|
||||
#[test]
|
||||
fn eq() {
|
||||
let arr = [
|
||||
nrange!(1..3, 1..3),
|
||||
nrange!(1..3; 2),
|
||||
NRange::new([1..3, 1..3]),
|
||||
];
|
||||
|
||||
for a in &arr {
|
||||
for b in &arr {
|
||||
assert_eq!(a, b);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn order() {
|
||||
let mut nd = nrange!(0..2, 0..2).into_iter();
|
||||
assert_eq!(nd.next(), Some([0, 0]));
|
||||
assert_eq!(nd.next(), Some([1, 0]));
|
||||
assert_eq!(nd.next(), Some([0, 1]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn emptiness() {
|
||||
let nd = nrange!(0..100, 0..99999, 0..0).into_iter();
|
||||
assert_eq!(nd.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn length() {
|
||||
let nd = nrange!(0..100, 0..100, 0..33).into_iter();
|
||||
assert_eq!(nd.len(), 100 * 100 * 33);
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn nd_benchmark(b: &mut Bencher) {
|
||||
b.iter(|| {
|
||||
for i in nrange!(0..100; 3).into_iter() {
|
||||
black_box(i);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn linear_benchmark(b: &mut Bencher) {
|
||||
b.iter(|| {
|
||||
for x in (0..100).into_iter() {
|
||||
for y in (0..100).into_iter() {
|
||||
for z in (0..100).into_iter() {
|
||||
black_box(&[x, y, z]);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
Loading…
Reference in a new issue