refactor: re-implemented SortedVec (#22)

* refactor: re-implemented SortedVec

* feat: add methods to remove items from SortedVec
This commit is contained in:
Badewanne3
2023-11-10 15:08:52 +01:00
committed by GitHub
parent 5456be3ad7
commit 3492a9472e
8 changed files with 310 additions and 166 deletions
+1
View File
@@ -3,6 +3,7 @@ Cargo.lock
output*
/.idea
/tests/custom.rs
/.idea
/pp-gen/.env
/pp-gen/target
+8 -1
View File
@@ -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
View File
@@ -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
+293
View File
@@ -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
View File
@@ -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
View File
@@ -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)
-3
View File
@@ -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,
-155
View File
@@ -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);
}
}