#![warn(missing_docs, clippy::all, clippy::pedantic)]
use ndarray::prelude::*;
use std::ops::{Index, Range};
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Edges<A: Ord> {
edges: Vec<A>,
}
impl<A: Ord> From<Vec<A>> for Edges<A> {
fn from(mut edges: Vec<A>) -> Self {
edges.sort_unstable();
edges.dedup();
Edges { edges }
}
}
impl<A: Ord + Clone> From<Array1<A>> for Edges<A> {
fn from(edges: Array1<A>) -> Self {
let edges = edges.to_vec();
Self::from(edges)
}
}
impl<A: Ord> Index<usize> for Edges<A> {
type Output = A;
fn index(&self, i: usize) -> &Self::Output {
&self.edges[i]
}
}
impl<A: Ord> Edges<A> {
#[must_use]
pub fn len(&self) -> usize {
self.edges.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.edges.is_empty()
}
#[must_use]
pub fn as_array_view(&self) -> ArrayView1<'_, A> {
ArrayView1::from(&self.edges)
}
pub fn indices_of(&self, value: &A) -> Option<(usize, usize)> {
let n_edges = self.len();
match self.edges.binary_search(value) {
Ok(i) if i == n_edges - 1 => None,
Ok(i) => Some((i, i + 1)),
Err(i) => match i {
0 => None,
j if j == n_edges => None,
j => Some((j - 1, j)),
},
}
}
pub fn iter(&self) -> impl Iterator<Item = &A> {
self.edges.iter()
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Bins<A: Ord> {
edges: Edges<A>,
}
impl<A: Ord> Bins<A> {
#[must_use]
pub fn new(edges: Edges<A>) -> Self {
Bins { edges }
}
#[must_use]
pub fn len(&self) -> usize {
match self.edges.len() {
0 => 0,
n => n - 1,
}
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn index_of(&self, value: &A) -> Option<usize> {
self.edges.indices_of(value).map(|t| t.0)
}
pub fn range_of(&self, value: &A) -> Option<Range<A>>
where
A: Clone,
{
let edges_indexes = self.edges.indices_of(value);
edges_indexes.map(|(left, right)| Range {
start: self.edges[left].clone(),
end: self.edges[right].clone(),
})
}
#[must_use]
pub fn index(&self, index: usize) -> Range<A>
where
A: Clone,
{
Range {
start: self.edges[index].clone(),
end: self.edges[index + 1].clone(),
}
}
}
#[cfg(test)]
mod edges_tests {
use super::{Array1, Edges};
use quickcheck_macros::quickcheck;
use std::collections::BTreeSet;
use std::iter::FromIterator;
#[quickcheck]
fn check_sorted_from_vec(v: Vec<i32>) -> bool {
let edges = Edges::from(v);
let n = edges.len();
for i in 1..n {
if edges[i - 1] > edges[i] {
return false;
}
}
true
}
#[quickcheck]
fn check_sorted_from_array(v: Vec<i32>) -> bool {
let a = Array1::from(v);
let edges = Edges::from(a);
let n = edges.len();
for i in 1..n {
if edges[i - 1] > edges[i] {
return false;
}
}
true
}
#[quickcheck]
fn edges_are_right_open(v: Vec<i32>) -> bool {
let edges = Edges::from(v);
let view = edges.as_array_view();
if view.is_empty() {
true
} else {
let last = view[view.len() - 1];
edges.indices_of(&last).is_none()
}
}
#[quickcheck]
fn edges_are_left_closed(v: Vec<i32>) -> bool {
let edges = Edges::from(v);
if let 1 = edges.len() {
true
} else {
let view = edges.as_array_view();
if view.is_empty() {
true
} else {
let first = view[0];
edges.indices_of(&first).is_some()
}
}
}
#[quickcheck]
#[allow(clippy::needless_pass_by_value)]
fn edges_are_deduped(v: Vec<i32>) -> bool {
let unique_elements = BTreeSet::from_iter(v.iter());
let edges = Edges::from(v.clone());
let view = edges.as_array_view();
let unique_edges = BTreeSet::from_iter(view.iter());
unique_edges == unique_elements
}
}
#[cfg(test)]
mod bins_tests {
use super::{Bins, Edges};
#[test]
#[should_panic]
#[allow(unused_must_use)]
fn get_panics_for_out_of_bounds_indexes() {
let edges = Edges::from(vec![0]);
let bins = Bins::new(edges);
bins.index(0);
}
}