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*
|
||||
/.idea
|
||||
/tests/custom.rs
|
||||
/.idea
|
||||
|
||||
/pp-gen/.env
|
||||
/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.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 {
|
||||
#[inline]
|
||||
fn default() -> Self {
|
||||
|
||||
+3
-1
@@ -1,6 +1,6 @@
|
||||
use std::{borrow::Cow, cmp::Ordering, collections::HashMap};
|
||||
|
||||
use crate::{parse::HitObject, util::SortedVec};
|
||||
use crate::parse::HitObject;
|
||||
|
||||
pub use self::{
|
||||
attributes::{BeatmapAttributes, BeatmapAttributesBuilder, BeatmapHitWindows},
|
||||
@@ -8,6 +8,7 @@ pub use self::{
|
||||
control_points::{DifficultyPoint, EffectPoint, TimingPoint},
|
||||
ext::*,
|
||||
mode::GameMode,
|
||||
sorted_vec::{Sortable, SortedVec},
|
||||
};
|
||||
|
||||
mod attributes;
|
||||
@@ -16,6 +17,7 @@ mod control_points;
|
||||
mod converts;
|
||||
mod ext;
|
||||
mod mode;
|
||||
mod sorted_vec;
|
||||
|
||||
/// The main beatmap struct containing all data relevant
|
||||
/// 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
|
||||
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")]
|
||||
mod gradual;
|
||||
@@ -231,7 +231,6 @@ pub use taiko::{TaikoPP, TaikoStars};
|
||||
|
||||
pub use mods::Mods;
|
||||
pub use parse::{ParseError, ParseResult};
|
||||
pub use util::SortedVec;
|
||||
|
||||
/// The result of calculating the strains on a map.
|
||||
/// 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 crate::{
|
||||
beatmap::{Beatmap, Break, DifficultyPoint, EffectPoint, GameMode, TimingPoint},
|
||||
util::{SortedVec, TandemSorter},
|
||||
beatmap::{Beatmap, Break, DifficultyPoint, EffectPoint, GameMode, SortedVec, TimingPoint},
|
||||
util::TandemSorter,
|
||||
};
|
||||
|
||||
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 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 {
|
||||
$self.difficulty_points.push_if_not_redundant(point);
|
||||
$self.difficulty_points.push(point);
|
||||
}
|
||||
|
||||
Ok(empty)
|
||||
|
||||
@@ -2,11 +2,8 @@ mod byte_hasher;
|
||||
mod compact_vec;
|
||||
mod float_ext;
|
||||
mod limited_queue;
|
||||
mod sorted_vec;
|
||||
mod tandem_sort;
|
||||
|
||||
pub use self::sorted_vec::SortedVec;
|
||||
|
||||
pub(crate) use self::{
|
||||
byte_hasher::ByteHasher, compact_vec::CompactVec, float_ext::FloatExt,
|
||||
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