refactor: re-implemented SortedVec (#22)
* refactor: re-implemented SortedVec * feat: add methods to remove items from SortedVec
This commit is contained in:
@@ -3,6 +3,7 @@ Cargo.lock
|
|||||||
output*
|
output*
|
||||||
/.idea
|
/.idea
|
||||||
/tests/custom.rs
|
/tests/custom.rs
|
||||||
|
/.idea
|
||||||
|
|
||||||
/pp-gen/.env
|
/pp-gen/.env
|
||||||
/pp-gen/target
|
/pp-gen/target
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ impl DifficultyPoint {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn is_redundant(&self, existing: &DifficultyPoint) -> bool {
|
pub(crate) fn is_redundant(&self, existing: &Self) -> bool {
|
||||||
(self.slider_vel - existing.slider_vel).abs() <= f64::EPSILON
|
(self.slider_vel - existing.slider_vel).abs() <= f64::EPSILON
|
||||||
&& self.generate_ticks == existing.generate_ticks
|
&& self.generate_ticks == existing.generate_ticks
|
||||||
}
|
}
|
||||||
@@ -118,6 +118,13 @@ impl EffectPoint {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl PartialOrd for EffectPoint {
|
||||||
|
#[inline]
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||||
|
self.time.partial_cmp(&other.time)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Default for EffectPoint {
|
impl Default for EffectPoint {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
|
|||||||
+3
-1
@@ -1,6 +1,6 @@
|
|||||||
use std::{borrow::Cow, cmp::Ordering, collections::HashMap};
|
use std::{borrow::Cow, cmp::Ordering, collections::HashMap};
|
||||||
|
|
||||||
use crate::{parse::HitObject, util::SortedVec};
|
use crate::parse::HitObject;
|
||||||
|
|
||||||
pub use self::{
|
pub use self::{
|
||||||
attributes::{BeatmapAttributes, BeatmapAttributesBuilder, BeatmapHitWindows},
|
attributes::{BeatmapAttributes, BeatmapAttributesBuilder, BeatmapHitWindows},
|
||||||
@@ -8,6 +8,7 @@ pub use self::{
|
|||||||
control_points::{DifficultyPoint, EffectPoint, TimingPoint},
|
control_points::{DifficultyPoint, EffectPoint, TimingPoint},
|
||||||
ext::*,
|
ext::*,
|
||||||
mode::GameMode,
|
mode::GameMode,
|
||||||
|
sorted_vec::{Sortable, SortedVec},
|
||||||
};
|
};
|
||||||
|
|
||||||
mod attributes;
|
mod attributes;
|
||||||
@@ -16,6 +17,7 @@ mod control_points;
|
|||||||
mod converts;
|
mod converts;
|
||||||
mod ext;
|
mod ext;
|
||||||
mod mode;
|
mod mode;
|
||||||
|
mod sorted_vec;
|
||||||
|
|
||||||
/// The main beatmap struct containing all data relevant
|
/// The main beatmap struct containing all data relevant
|
||||||
/// for difficulty and performance calculation
|
/// for difficulty and performance calculation
|
||||||
|
|||||||
@@ -0,0 +1,293 @@
|
|||||||
|
use std::{
|
||||||
|
cmp::Ordering,
|
||||||
|
fmt::{Debug, Formatter, Result as FmtResult},
|
||||||
|
ops::{Deref, Index},
|
||||||
|
slice::SliceIndex,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::beatmap::{DifficultyPoint, EffectPoint, TimingPoint};
|
||||||
|
|
||||||
|
/// A [`Vec`] whose elements are guaranteed to be unique and in order.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct SortedVec<T> {
|
||||||
|
inner: Vec<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> SortedVec<T> {
|
||||||
|
/// Constructs a new, empty `SortedVec<T>`.
|
||||||
|
#[inline]
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self { inner: Vec::new() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Constructs a new, empty `SortedVec<T>` with at least the specified capacity.
|
||||||
|
#[inline]
|
||||||
|
pub fn with_capacity(capacity: usize) -> Self {
|
||||||
|
Self {
|
||||||
|
inner: Vec::with_capacity(capacity),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extracts the inner [`Vec`].
|
||||||
|
#[inline]
|
||||||
|
pub fn into_inner(self) -> Vec<T> {
|
||||||
|
self.inner
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extracts a slice containing the entire sorted vector.
|
||||||
|
pub fn as_slice(&self) -> &[T] {
|
||||||
|
self.inner.as_slice()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a mutable reference to the underlying `Vec`.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// The caller must ensure that the items stay in order.
|
||||||
|
pub unsafe fn as_inner_mut(&mut self) -> &mut Vec<T> {
|
||||||
|
&mut self.inner
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes the last element and returns it, or `None` if the vec is empty.
|
||||||
|
pub fn pop(&mut self) -> Option<T> {
|
||||||
|
self.inner.pop()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retains only the elements specified by the predicate.
|
||||||
|
///
|
||||||
|
/// In other words, remove all items `i` for which `f(&i)` returns `false`.
|
||||||
|
pub fn retain<F>(&mut self, f: F)
|
||||||
|
where
|
||||||
|
F: FnMut(&T) -> bool,
|
||||||
|
{
|
||||||
|
self.inner.retain(f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Sortable> SortedVec<T> {
|
||||||
|
/// Same as [`slice::binary_search_by`] with the function
|
||||||
|
/// [`<T as Sortable>::cmp`](Sortable::cmp).
|
||||||
|
#[inline]
|
||||||
|
pub fn find(&self, value: &T) -> Result<usize, usize> {
|
||||||
|
self.inner
|
||||||
|
.binary_search_by(|probe| <T as Sortable>::cmp(probe, value))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Push a new value into the sorted list based on [`<T as Sortable>::push`](Sortable::push).
|
||||||
|
pub fn push(&mut self, value: T) {
|
||||||
|
<T as Sortable>::push(value, self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Deref for SortedVec<T> {
|
||||||
|
type Target = [T];
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
<Vec<T> as Deref>::deref(&self.inner)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, I> Index<I> for SortedVec<T>
|
||||||
|
where
|
||||||
|
I: SliceIndex<[T]>,
|
||||||
|
{
|
||||||
|
type Output = <I as SliceIndex<[T]>>::Output;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn index(&self, index: I) -> &Self::Output {
|
||||||
|
<Vec<T> as Index<I>>::index(&self.inner, index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Debug> Debug for SortedVec<T> {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
|
||||||
|
<Vec<T> as Debug>::fmt(&self.inner, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Default for SortedVec<T> {
|
||||||
|
#[inline]
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Sortable> From<Vec<T>> for SortedVec<T> {
|
||||||
|
fn from(mut v: Vec<T>) -> Self {
|
||||||
|
v.sort_by(<T as Sortable>::cmp);
|
||||||
|
v.dedup_by(|a, b| {
|
||||||
|
<T as Sortable>::cmp(a, b) == Ordering::Equal || <T as Sortable>::is_redundant(b, a)
|
||||||
|
});
|
||||||
|
|
||||||
|
Self { inner: v }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Sortable> FromIterator<T> for SortedVec<T> {
|
||||||
|
fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
|
||||||
|
Self::from(Vec::from_iter(iter))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Sortable> Extend<T> for SortedVec<T> {
|
||||||
|
fn extend<I: IntoIterator<Item = T>>(&mut self, iter: I) {
|
||||||
|
for value in iter {
|
||||||
|
self.push(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Trait for types that can be sorted in a [`SortedVec`].
|
||||||
|
pub trait Sortable: Sized {
|
||||||
|
/// An [`Ordering`] between `self` and `other`.
|
||||||
|
fn cmp(&self, other: &Self) -> Ordering;
|
||||||
|
|
||||||
|
/// Indicates whether `self` and `_existing` are identical.
|
||||||
|
#[inline]
|
||||||
|
fn is_redundant(&self, _existing: &Self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pushes a value into the [`SortedVec`].
|
||||||
|
#[inline]
|
||||||
|
fn push(self, sorted_vec: &mut SortedVec<Self>) {
|
||||||
|
match sorted_vec.find(&self) {
|
||||||
|
Ok(i) => sorted_vec.inner[i] = self,
|
||||||
|
Err(i) if i == sorted_vec.len() => sorted_vec.inner.push(self),
|
||||||
|
Err(i) => sorted_vec.inner.insert(i, self),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sortable for TimingPoint {
|
||||||
|
#[inline]
|
||||||
|
fn cmp(&self, other: &Self) -> Ordering {
|
||||||
|
self.partial_cmp(other).unwrap_or(Ordering::Equal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sortable for DifficultyPoint {
|
||||||
|
#[inline]
|
||||||
|
fn cmp(&self, other: &Self) -> Ordering {
|
||||||
|
self.partial_cmp(other).unwrap_or(Ordering::Equal)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn is_redundant(&self, existing: &Self) -> bool {
|
||||||
|
self.is_redundant(existing)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push(self, sorted_vec: &mut SortedVec<Self>) {
|
||||||
|
enum Action {
|
||||||
|
Insert(usize),
|
||||||
|
Replace(usize),
|
||||||
|
Push,
|
||||||
|
Skip,
|
||||||
|
}
|
||||||
|
|
||||||
|
let action = match sorted_vec.find(&self).map_err(|idx| idx.checked_sub(1)) {
|
||||||
|
Ok(i) | Err(Some(i)) if self.is_redundant(&sorted_vec[i]) => Action::Skip,
|
||||||
|
Ok(i) => Action::Replace(i),
|
||||||
|
Err(Some(i)) if i == sorted_vec.len() - 1 => Action::Push,
|
||||||
|
Err(Some(i)) => Action::Insert(i),
|
||||||
|
Err(None) if self.is_redundant(&Self::default()) => Action::Skip,
|
||||||
|
Err(None) => Action::Insert(0),
|
||||||
|
};
|
||||||
|
|
||||||
|
match action {
|
||||||
|
Action::Insert(i) => sorted_vec.inner.insert(i, self),
|
||||||
|
Action::Replace(i) => sorted_vec.inner[i] = self,
|
||||||
|
Action::Push => sorted_vec.inner.push(self),
|
||||||
|
Action::Skip => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sortable for EffectPoint {
|
||||||
|
#[inline]
|
||||||
|
fn cmp(&self, other: &Self) -> Ordering {
|
||||||
|
self.partial_cmp(other).unwrap_or(Ordering::Equal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Ord> Sortable for T {
|
||||||
|
#[inline]
|
||||||
|
fn cmp(&self, other: &Self) -> Ordering {
|
||||||
|
<Self as Ord>::cmp(self, other)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::beatmap::DifficultyPoint;
|
||||||
|
|
||||||
|
use super::SortedVec;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sorts_on_push() {
|
||||||
|
let mut v = SortedVec::with_capacity(4);
|
||||||
|
|
||||||
|
v.push(42);
|
||||||
|
v.push(13);
|
||||||
|
v.push(20);
|
||||||
|
v.push(0);
|
||||||
|
|
||||||
|
assert_eq!(v.as_slice(), &[0_i32, 13, 20, 42]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_push_if_redundant() {
|
||||||
|
let mut v = SortedVec::default();
|
||||||
|
|
||||||
|
v.push(DifficultyPoint::default());
|
||||||
|
assert_eq!(v.len(), 0);
|
||||||
|
|
||||||
|
v.push(DifficultyPoint::new(1.0, 2.0, 3.0));
|
||||||
|
assert_eq!(v.len(), 1);
|
||||||
|
|
||||||
|
v.push(DifficultyPoint::new(2.0, 2.0, 3.0));
|
||||||
|
v.push(DifficultyPoint::default());
|
||||||
|
assert_eq!(v.len(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn from_iter() {
|
||||||
|
let base = vec![
|
||||||
|
DifficultyPoint {
|
||||||
|
time: 5.0,
|
||||||
|
slider_vel: 10.0,
|
||||||
|
bpm_mult: 1.0,
|
||||||
|
generate_ticks: true,
|
||||||
|
},
|
||||||
|
DifficultyPoint {
|
||||||
|
time: 3.0,
|
||||||
|
slider_vel: 20.0,
|
||||||
|
bpm_mult: 2.0,
|
||||||
|
generate_ticks: false,
|
||||||
|
},
|
||||||
|
DifficultyPoint {
|
||||||
|
time: 6.0,
|
||||||
|
slider_vel: 10.0,
|
||||||
|
bpm_mult: 3.0,
|
||||||
|
generate_ticks: true,
|
||||||
|
},
|
||||||
|
DifficultyPoint {
|
||||||
|
time: 10.0,
|
||||||
|
slider_vel: 15.0,
|
||||||
|
bpm_mult: 4.0,
|
||||||
|
generate_ticks: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
let sorted = SortedVec::from_iter(base);
|
||||||
|
|
||||||
|
let v: Vec<_> = sorted
|
||||||
|
.into_inner()
|
||||||
|
.into_iter()
|
||||||
|
.map(|tp| tp.bpm_mult)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
assert_eq!(v, vec![2.0, 1.0, 4.0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-2
@@ -202,7 +202,7 @@ pub mod parse;
|
|||||||
|
|
||||||
/// Beatmap and contained types
|
/// Beatmap and contained types
|
||||||
pub mod beatmap;
|
pub mod beatmap;
|
||||||
pub use beatmap::{Beatmap, BeatmapExt, GameMode};
|
pub use beatmap::{Beatmap, BeatmapExt, GameMode, SortedVec}; // TODO: remove SortedVec on next breaking release
|
||||||
|
|
||||||
#[cfg(feature = "gradual")]
|
#[cfg(feature = "gradual")]
|
||||||
mod gradual;
|
mod gradual;
|
||||||
@@ -231,7 +231,6 @@ pub use taiko::{TaikoPP, TaikoStars};
|
|||||||
|
|
||||||
pub use mods::Mods;
|
pub use mods::Mods;
|
||||||
pub use parse::{ParseError, ParseResult};
|
pub use parse::{ParseError, ParseResult};
|
||||||
pub use util::SortedVec;
|
|
||||||
|
|
||||||
/// The result of calculating the strains on a map.
|
/// The result of calculating the strains on a map.
|
||||||
/// Suitable to plot the difficulty of a map over time.
|
/// Suitable to plot the difficulty of a map over time.
|
||||||
|
|||||||
+4
-4
@@ -29,8 +29,8 @@ use std::path::Path;
|
|||||||
use async_std::{fs::File, io::Read as AsyncRead, path::Path};
|
use async_std::{fs::File, io::Read as AsyncRead, path::Path};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
beatmap::{Beatmap, Break, DifficultyPoint, EffectPoint, GameMode, TimingPoint},
|
beatmap::{Beatmap, Break, DifficultyPoint, EffectPoint, GameMode, SortedVec, TimingPoint},
|
||||||
util::{SortedVec, TandemSorter},
|
util::TandemSorter,
|
||||||
};
|
};
|
||||||
|
|
||||||
trait InRange: Sized + Copy + Neg<Output = Self> + PartialOrd + FromStr {
|
trait InRange: Sized + Copy + Neg<Output = Self> + PartialOrd + FromStr {
|
||||||
@@ -355,7 +355,7 @@ macro_rules! parse_timingpoints_body {
|
|||||||
|
|
||||||
if time != pending_diff_points_time {
|
if time != pending_diff_points_time {
|
||||||
if let Some(point) = pending_diff_point.take() {
|
if let Some(point) = pending_diff_point.take() {
|
||||||
$self.difficulty_points.push_if_not_redundant(point);
|
$self.difficulty_points.push(point);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -376,7 +376,7 @@ macro_rules! parse_timingpoints_body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(point) = pending_diff_point {
|
if let Some(point) = pending_diff_point {
|
||||||
$self.difficulty_points.push_if_not_redundant(point);
|
$self.difficulty_points.push(point);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(empty)
|
Ok(empty)
|
||||||
|
|||||||
@@ -2,11 +2,8 @@ mod byte_hasher;
|
|||||||
mod compact_vec;
|
mod compact_vec;
|
||||||
mod float_ext;
|
mod float_ext;
|
||||||
mod limited_queue;
|
mod limited_queue;
|
||||||
mod sorted_vec;
|
|
||||||
mod tandem_sort;
|
mod tandem_sort;
|
||||||
|
|
||||||
pub use self::sorted_vec::SortedVec;
|
|
||||||
|
|
||||||
pub(crate) use self::{
|
pub(crate) use self::{
|
||||||
byte_hasher::ByteHasher, compact_vec::CompactVec, float_ext::FloatExt,
|
byte_hasher::ByteHasher, compact_vec::CompactVec, float_ext::FloatExt,
|
||||||
limited_queue::LimitedQueue, tandem_sort::TandemSorter,
|
limited_queue::LimitedQueue, tandem_sort::TandemSorter,
|
||||||
|
|||||||
@@ -1,155 +0,0 @@
|
|||||||
use std::{
|
|
||||||
cmp::Ordering,
|
|
||||||
fmt::{Debug, Formatter, Result as FmtResult},
|
|
||||||
ops::{Deref, Index},
|
|
||||||
slice::SliceIndex,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::beatmap::{DifficultyPoint, EffectPoint, TimingPoint};
|
|
||||||
|
|
||||||
/// A [`Vec`] whose elements are guaranteed to be in order based on the given comparator.
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct SortedVec<T> {
|
|
||||||
inner: Vec<T>,
|
|
||||||
cmp: fn(&T, &T) -> Ordering,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> SortedVec<T> {
|
|
||||||
/// Same as [`slice::binary_search_by`] with the internal compare function
|
|
||||||
#[inline]
|
|
||||||
pub fn find(&self, value: &T) -> Result<usize, usize> {
|
|
||||||
self.inner
|
|
||||||
.binary_search_by(|probe| (self.cmp)(probe, value))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Extracts the inner [`Vec`].
|
|
||||||
#[inline]
|
|
||||||
pub fn into_inner(self) -> Vec<T> {
|
|
||||||
self.inner
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Push a new value into the sorted list.
|
|
||||||
/// If there is already an element that matches the new value,
|
|
||||||
/// the old element will be replaced.
|
|
||||||
pub(crate) fn push(&mut self, value: T) {
|
|
||||||
match self.find(&value) {
|
|
||||||
Ok(i) => self.inner[i] = value,
|
|
||||||
Err(i) if i == self.inner.len() => self.inner.push(value),
|
|
||||||
Err(i) => self.inner.insert(i, value),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Deref for SortedVec<T> {
|
|
||||||
type Target = [T];
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
<Vec<T> as Deref>::deref(&self.inner)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T, I> Index<I> for SortedVec<T>
|
|
||||||
where
|
|
||||||
I: SliceIndex<[T]>,
|
|
||||||
{
|
|
||||||
type Output = <I as SliceIndex<[T]>>::Output;
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn index(&self, index: I) -> &Self::Output {
|
|
||||||
<Vec<T> as Index<I>>::index(&self.inner, index)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Debug> Debug for SortedVec<T> {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
|
|
||||||
<Vec<T> as Debug>::fmt(&self.inner, f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Default for SortedVec<T>
|
|
||||||
where
|
|
||||||
T: Ord,
|
|
||||||
{
|
|
||||||
#[inline]
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
inner: Vec::default(),
|
|
||||||
cmp: <T as Ord>::cmp,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! impl_default_control_point {
|
|
||||||
( $( $ty:ident ),* ) => {
|
|
||||||
$(
|
|
||||||
impl Default for SortedVec<$ty> {
|
|
||||||
#[inline]
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
inner: Vec::new(),
|
|
||||||
cmp: |a, b| a.time.partial_cmp(&b.time).unwrap_or(Ordering::Equal),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SortedVec<$ty> {
|
|
||||||
#[allow(unused)]
|
|
||||||
pub(crate) fn with_capacity(capacity: usize) -> Self {
|
|
||||||
Self {
|
|
||||||
inner: Vec::with_capacity(capacity),
|
|
||||||
cmp: |a, b| a.time.partial_cmp(&b.time).unwrap_or(Ordering::Equal),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)*
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl_default_control_point!(TimingPoint, DifficultyPoint, EffectPoint);
|
|
||||||
|
|
||||||
impl SortedVec<DifficultyPoint> {
|
|
||||||
pub(crate) fn push_if_not_redundant(&mut self, value: DifficultyPoint) {
|
|
||||||
let is_redundant = match self.find(&value).map_err(|idx| idx.checked_sub(1)) {
|
|
||||||
Ok(idx) | Err(Some(idx)) => value.is_redundant(&self[idx]),
|
|
||||||
Err(None) => value.is_redundant(&DifficultyPoint::default()),
|
|
||||||
};
|
|
||||||
|
|
||||||
if !is_redundant {
|
|
||||||
self.push(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use crate::beatmap::DifficultyPoint;
|
|
||||||
|
|
||||||
use super::SortedVec;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn sorts_on_push() {
|
|
||||||
let mut v = SortedVec {
|
|
||||||
inner: Vec::new(),
|
|
||||||
cmp: <i32 as Ord>::cmp,
|
|
||||||
};
|
|
||||||
|
|
||||||
v.push(42);
|
|
||||||
v.push(13);
|
|
||||||
v.push(20);
|
|
||||||
v.push(0);
|
|
||||||
|
|
||||||
assert_eq!(&v[..], &[0_i32, 13, 20, 42]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn no_push_if_redundant() {
|
|
||||||
let mut v = SortedVec::default();
|
|
||||||
|
|
||||||
v.push(DifficultyPoint::default());
|
|
||||||
assert_eq!(v.len(), 1);
|
|
||||||
|
|
||||||
v.push_if_not_redundant(DifficultyPoint::default());
|
|
||||||
assert_eq!(v.len(), 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user