use std::collections::BTreeMap;
use crate::{Point, PointKey};
use super::SegId;
#[derive(Clone, Debug, Default)]
pub(super) struct SharedStarts {
by_point: BTreeMap<PointKey, Vec<SegId>>,
}
impl SharedStarts {
pub(super) const fn new() -> Self {
Self {
by_point: BTreeMap::new(),
}
}
pub(super) fn segments(&self, point: Point) -> &[SegId] {
self.by_point
.get(&point.key())
.map_or(&[], |segments| segments.as_slice())
}
pub(super) fn sole_segment(&self, point: Point) -> Option<SegId> {
match self.segments(point) {
[only] => Some(*only),
_ => None,
}
}
pub(super) fn add(&mut self, point: Point, segment: SegId) {
let segments = self.by_point.entry(point.key()).or_default();
if !segments.contains(&segment) {
segments.push(segment);
}
}
pub(super) fn remove(&mut self, point: Point, segment: SegId) {
let key = point.key();
let Some(segments) = self.by_point.get_mut(&key) else {
return;
};
segments.retain(|id| *id != segment);
if segments.is_empty() {
self.by_point.remove(&key);
}
}
pub(super) fn iter(&self) -> impl Iterator<Item = (PointKey, &[SegId])> {
self.by_point
.iter()
.map(|(key, segments)| (*key, segments.as_slice()))
}
pub(super) fn len(&self) -> usize {
self.by_point.values().map(Vec::len).sum()
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used, clippy::expect_used)]
use super::SharedStarts;
use crate::Point;
use crate::clipping::SegId;
fn p(x: f64, y: f64) -> Point {
Point::new(x, y)
}
#[test]
fn segments_group_by_exact_point() {
let mut index = SharedStarts::new();
index.add(p(1.0, 2.0), SegId::new(0));
index.add(p(1.0, 2.0), SegId::new(1));
index.add(p(1.0, 2.000_000_1), SegId::new(2));
assert_eq!(index.segments(p(1.0, 2.0)), [SegId::new(0), SegId::new(1)]);
assert_eq!(index.segments(p(1.0, 2.000_000_1)), [SegId::new(2)]);
assert_eq!(index.len(), 3);
}
#[test]
fn there_is_no_tolerance_in_the_key() {
let mut index = SharedStarts::new();
index.add(p(1.0, 2.0), SegId::new(0));
let one_bit_up = f64::from_bits(2.0_f64.to_bits() + 1);
assert!(index.segments(p(1.0, one_bit_up)).is_empty());
}
#[test]
fn the_two_zeroes_are_one_point() {
let mut index = SharedStarts::new();
index.add(p(-0.0, 0.0), SegId::new(0));
assert_eq!(index.segments(p(0.0, -0.0)), [SegId::new(0)]);
}
#[test]
fn adding_the_same_segment_twice_is_idempotent() {
let mut index = SharedStarts::new();
index.add(p(1.0, 2.0), SegId::new(0));
index.add(p(1.0, 2.0), SegId::new(0));
assert_eq!(index.segments(p(1.0, 2.0)), [SegId::new(0)]);
}
#[test]
fn the_sole_segment_is_reported_only_when_it_is_alone() {
let mut index = SharedStarts::new();
assert_eq!(index.sole_segment(p(1.0, 2.0)), None);
index.add(p(1.0, 2.0), SegId::new(0));
assert_eq!(index.sole_segment(p(1.0, 2.0)), Some(SegId::new(0)));
index.add(p(1.0, 2.0), SegId::new(1));
assert_eq!(index.sole_segment(p(1.0, 2.0)), None);
index.remove(p(1.0, 2.0), SegId::new(1));
assert_eq!(index.sole_segment(p(1.0, 2.0)), Some(SegId::new(0)));
}
#[test]
fn an_emptied_point_leaves_no_entry_behind() {
let mut index = SharedStarts::new();
index.add(p(1.0, 2.0), SegId::new(0));
index.remove(p(1.0, 2.0), SegId::new(0));
assert_eq!(index.iter().count(), 0);
assert_eq!(index.len(), 0);
}
}