use std::{
cmp::Ordering,
fmt::{self, Display, Formatter},
};
use derive_more::Constructor;
use getset::{Getters, MutGetters, Setters};
#[derive(Constructor, Debug, Default, Clone, Getters, Setters, PartialEq)]
pub struct Point {
#[getset(get = "pub", set = "pub")]
number: f64,
#[getset(get = "pub", set = "pub")]
mark: String,
}
impl Display for Point {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
writeln!(f, "Point:\t{}\t{}", self.number, self.mark)
}
}
#[derive(Clone, Constructor, Debug, Default, Getters, MutGetters, Setters, PartialEq)]
pub struct Tier {
#[getset(get = "pub", set = "pub")]
name: String,
#[getset(get = "pub")]
xmin: f64,
#[getset(get = "pub")]
xmax: f64,
#[getset(get = "pub", get_mut = "pub", set = "pub")]
points: Vec<Point>,
}
impl Tier {
pub fn set_xmin(&mut self, xmin: f64, warn: Option<bool>) {
if warn.is_some_and(|b| b) {
let min_point = self
.points
.iter()
.filter_map(|point| {
point
.number
.partial_cmp(&f64::INFINITY)
.map(|_| point.number)
})
.min_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Greater));
if min_point.is_some_and(|min| xmin > min) {
eprintln!(
"Warning: Tier `{}` has a minimum point of {} but the set xmin is {}",
self.name,
min_point.unwrap_or_default(),
xmin
);
}
}
self.xmin = xmin;
}
pub fn set_xmax(&mut self, xmax: f64, warn: Option<bool>) {
if warn.is_some_and(|b| b) {
let max_point = self
.points
.iter()
.filter_map(|point| {
point
.number
.partial_cmp(&f64::INFINITY)
.map(|_| point.number)
})
.max_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Less));
if max_point.is_some_and(|max| xmax < max) {
eprintln!(
"Warning: Tier `{}` has a maximum point of {} but the set xmax is {}",
self.name,
max_point.unwrap_or_default(),
xmax
);
}
}
self.xmax = xmax;
}
#[must_use]
pub fn get_size(&self) -> usize {
self.points.len()
}
pub fn push_point<W: Into<Option<bool>> + Copy>(&mut self, point: Point, warn: W) {
if warn.into().unwrap_or_default() {
if point.number < self.xmin {
eprintln!(
"Warning: Tier `{}` has a number of {} but the tier has an xmin of {}",
self.name, point.number, self.xmin
);
}
if point.number > self.xmax {
eprintln!(
"Warning: Tier `{}` has a number of {} but the tier has an xmax of {}",
self.name, point.number, self.xmax
);
}
}
self.points.push(point);
self.reorder();
}
pub fn push_points<W: Into<Option<bool>> + Copy>(&mut self, points: Vec<Point>, warn: W) {
if warn.into().unwrap_or_default() {
for point in &points {
if point.number < self.xmin {
eprintln!(
"Warning: Tier `{}` has a number of {} but the tier has an xmin of {}",
self.name, point.number, self.xmin
);
}
if point.number > self.xmax {
eprintln!(
"Warning: Tier `{}` has a number of {} but the tier has an xmax of {}",
self.name, point.number, self.xmax
);
}
}
}
self.points.extend(points);
self.reorder();
}
#[must_use]
pub fn check_overlaps(&self) -> Option<Vec<(u64, u64)>> {
let mut overlaps: Vec<(u64, u64)> = Vec::new();
for (i, point) in self.points.iter().enumerate() {
for (j, other_point) in self.points.iter().enumerate() {
#[allow(clippy::float_cmp)]
if i != j && point.number == other_point.number {
overlaps.push((i as u64, j as u64));
}
}
}
if overlaps.is_empty() {
None
} else {
Some(overlaps)
}
}
pub fn reorder(&mut self) {
self.points
.sort_by(|a, b| a.number.partial_cmp(&b.number).unwrap_or(Ordering::Equal));
}
}
impl Display for Tier {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(
f,
"PointTier {}:
xmin: {}
xmax: {}
point count: {}",
self.name,
self.xmin,
self.xmax,
self.points.len()
)
}
}
#[cfg(test)]
#[allow(clippy::float_cmp)]
mod test_point {
#[test]
fn test_point() {
use crate::point::Point;
let point = Point::new(1.0, "test".to_string());
assert_eq!(point.number(), &1.0);
assert_eq!(point.mark(), "test");
assert_eq!(point.to_string(), "Point:\t1\ttest\n");
}
}
#[cfg(test)]
#[allow(clippy::float_cmp)]
mod test_point_tier {
#[test]
fn set_xmin() {
use crate::point::Tier;
let mut tier = Tier::new("test".to_string(), 0.0, 10.0, vec![]);
tier.set_xmin(5.0, Some(true));
assert_eq!(tier.xmin(), &5.0);
}
#[test]
fn set_xmax() {
use crate::point::Tier;
let mut tier = Tier::new("test".to_string(), 0.0, 10.0, vec![]);
tier.set_xmax(5.0, Some(true));
assert_eq!(tier.xmax(), &5.0);
}
#[test]
fn get_size() {
use crate::point::Tier;
let tier = Tier::new("test".to_string(), 0.0, 10.0, vec![]);
assert_eq!(tier.get_size(), 0);
}
#[test]
fn push_point() {
use crate::point::{Point, Tier};
let mut tier = Tier::new("test".to_string(), 0.0, 10.0, vec![]);
tier.push_point(Point::new(5.0, "test".to_string()), true);
assert_eq!(tier.get_size(), 1);
}
#[test]
fn push_points() {
use crate::point::{Point, Tier};
let mut tier = Tier::new("test".to_string(), 0.0, 10.0, vec![]);
tier.push_points(vec![Point::new(5.0, "test".to_string())], true);
assert_eq!(tier.get_size(), 1);
}
#[test]
fn check_overlaps() {
use crate::point::{Point, Tier};
let mut tier = Tier::new("test".to_string(), 0.0, 10.0, vec![]);
tier.push_points(
vec![
Point::new(5.0, "test".to_string()),
Point::new(5.0, "test".to_string()),
],
true,
);
assert_eq!(tier.check_overlaps(), Some(vec![(0, 1), (1, 0)]));
}
#[test]
fn reorder() {
use crate::point::{Point, Tier};
let mut tier = Tier::new(
"test".to_string(),
0.0,
10.0,
vec![
Point::new(5.0, "test".to_string()),
Point::new(3.0, "test".to_string()),
],
);
tier.reorder();
assert_eq!(tier.points()[0].number(), &3.0);
assert_eq!(tier.points()[1].number(), &5.0);
}
}