impl c#'s unstable sort & refactor sort things

This commit is contained in:
MaxOhn
2024-02-15 15:46:44 +01:00
parent 25eec40e06
commit 100d01483c
11 changed files with 213 additions and 59 deletions
+4 -2
View File
@@ -8,7 +8,7 @@ use crate::{
hit_object::{HitObject, HitObjectKind, HoldNote, Spinner},
mode::ConvertStatus,
},
util::{float_ext::FloatExt, random::Random},
util::{float_ext::FloatExt, random::Random, sort},
};
use super::{
@@ -75,7 +75,9 @@ pub fn convert_objects(
palpable_objects.extend(new_objects);
}
palpable_objects.sort_by(|a, b| a.start_time.total_cmp(&b.start_time));
sort::csharp(&mut palpable_objects, |a, b| {
a.start_time.total_cmp(&b.start_time)
});
initialize_hyper_dash(cs, &mut palpable_objects);
+2 -4
View File
@@ -11,9 +11,7 @@ use crate::{
hit_object::{HitObjectKind, HoldNote, Spinner},
mode::ConvertStatus,
},
util::{
float_ext::FloatExt, legacy_sort::legacy_sort, limited_queue::LimitedQueue, random::Random,
},
util::{float_ext::FloatExt, limited_queue::LimitedQueue, random::Random, sort},
};
use self::{
@@ -161,7 +159,7 @@ fn convert(map: &mut Beatmap) {
map.hit_objects = new_hit_objects;
map.hit_objects
.sort_by(|a, b| a.start_time.total_cmp(&b.start_time));
legacy_sort(&mut map.hit_objects);
sort::osu_legacy(&mut map.hit_objects);
map.mode = GameMode::Mania;
}
+3 -3
View File
@@ -22,7 +22,7 @@ use crate::{
},
hit_object::{HitObject, HitObjectKind, HoldNote, Slider, Spinner},
},
util::{float_ext::FloatExt, legacy_sort::legacy_sort, tandem_sort::TandemSorter},
util::{float_ext::FloatExt, sort},
};
use super::{Beatmap, DEFAULT_SLIDER_LENIENCY};
@@ -282,7 +282,7 @@ impl From<BeatmapState> for Beatmap {
slider_tick_rate,
} = state.difficulty.into();
let mut sorter = TandemSorter::new(
let mut sorter = sort::TandemSorter::new(
&state.hit_objects,
|a, b| a.start_time.total_cmp(&b.start_time),
true,
@@ -292,7 +292,7 @@ impl From<BeatmapState> for Beatmap {
sorter.sort(&mut state.hit_sounds);
if state.mode == GameMode::Mania {
legacy_sort(&mut state.hit_objects);
sort::osu_legacy(&mut state.hit_objects);
}
Beatmap {
+9 -4
View File
@@ -3,9 +3,12 @@ use rosu_map::{
util::Pos,
};
use crate::model::{
control_point::{DifficultyPoint, TimingPoint},
hit_object::{HitObject, HitObjectKind, HoldNote, Slider, Spinner},
use crate::{
model::{
control_point::{DifficultyPoint, TimingPoint},
hit_object::{HitObject, HitObjectKind, HoldNote, Slider, Spinner},
},
util::sort,
};
use super::{convert::OsuBeatmap, PLAYFIELD_BASE_SIZE};
@@ -255,7 +258,9 @@ impl OsuSlider {
})
.collect();
nested_objects.sort_unstable_by(|a, b| a.start_time.total_cmp(&b.start_time));
sort::csharp(&mut nested_objects, |a, b| {
a.start_time.total_cmp(&b.start_time)
});
let lazy_travel_time = nested_objects
.last()
+1 -1
View File
@@ -12,7 +12,7 @@ use crate::{
hit_object::{HitObject, HitObjectKind, HoldNote, Slider, Spinner},
mode::ConvertStatus,
},
util::{float_ext::FloatExt, tandem_sort::TandemSorter},
util::{float_ext::FloatExt, sort::TandemSorter},
};
use super::Taiko;
+1 -1
View File
@@ -78,7 +78,7 @@ impl Peaks {
let mut difficulty = 0.0;
let mut weight = 1.0;
peaks.sort_unstable_by(|a, b| b.total_cmp(a));
peaks.sort_by(f64::total_cmp);
for strain in peaks {
difficulty += strain * weight;
+1 -2
View File
@@ -1,7 +1,6 @@
pub mod float_ext;
pub mod legacy_sort;
pub mod limited_queue;
pub mod map_or_attrs;
pub mod mods;
pub mod random;
pub mod tandem_sort;
pub mod sort;
+122
View File
@@ -0,0 +1,122 @@
use std::cmp::Ordering;
/// C#'s unstable sorting algorithm.
///
/// <https://referencesource.microsoft.com/#mscorlib/system/collections/generic/list.cs,fc1bc8c930f6c93c,references>
pub fn sort<T, F>(keys: &mut [T], cmp: F)
where
F: Fn(&T, &T) -> Ordering,
{
introspective_sort(keys, 0, keys.len(), &cmp);
}
fn introspective_sort<T, F>(keys: &mut [T], left: usize, len: usize, cmp: &F)
where
F: Fn(&T, &T) -> Ordering,
{
if len >= 2 {
intro_sort(keys, left, len + left - 1, 2 * keys.len().ilog2(), cmp);
}
}
fn intro_sort<T, F>(keys: &mut [T], lo: usize, mut hi: usize, mut depth_limit: u32, cmp: &F)
where
F: Fn(&T, &T) -> Ordering,
{
const INTRO_SORT_SIZE_THRESHOLD: usize = 16;
while hi > lo {
let partition_size = hi - lo + 1;
if partition_size <= INTRO_SORT_SIZE_THRESHOLD {
match partition_size {
1 => {}
2 => super::swap_if_greater(keys, cmp, lo, hi),
3 => {
super::swap_if_greater(keys, cmp, lo, hi - 1);
super::swap_if_greater(keys, cmp, lo, hi);
super::swap_if_greater(keys, cmp, hi - 1, hi);
}
_ => insertion_sort(keys, lo, hi, cmp),
}
break;
}
if depth_limit == 0 {
super::heap_sort(keys, lo, hi, cmp);
break;
}
depth_limit -= 1;
let p = pick_pivot_and_partition(keys, lo, hi, cmp);
intro_sort(keys, p + 1, hi, depth_limit, cmp);
hi = p - 1;
}
}
fn pick_pivot_and_partition<T, F>(keys: &mut [T], lo: usize, hi: usize, cmp: &F) -> usize
where
F: Fn(&T, &T) -> Ordering,
{
let mid = lo + (hi - lo) / 2;
super::swap_if_greater(keys, cmp, lo, mid);
super::swap_if_greater(keys, cmp, lo, hi);
super::swap_if_greater(keys, cmp, mid, hi);
super::swap(keys, mid, hi - 1);
let mut left = lo;
let mut right = hi - 1;
let mut pivot_idx = right;
let mut next_left = true;
while left < right {
while {
left += 1;
cmp(&keys[left], &keys[pivot_idx]).is_lt()
} {}
while {
right -= 1;
cmp(&keys[pivot_idx], &keys[right]).is_lt()
} {}
if left >= right {
break;
}
super::swap(keys, left, right);
if next_left {
pivot_idx = left;
} else {
pivot_idx = right;
}
next_left = !next_left;
}
super::swap(keys, left, hi - 1);
left
}
fn insertion_sort<T, F>(keys: &mut [T], lo: usize, hi: usize, cmp: F)
where
F: Fn(&T, &T) -> Ordering,
{
for i in lo..hi {
let target = &keys[i + 1];
let smaller_idx = keys[lo..=i]
.iter()
.rposition(|curr| cmp(target, curr).is_ge());
if let Some(idx) = smaller_idx {
keys[lo + idx + 1..=i + 1].rotate_right(1);
}
}
}
+58
View File
@@ -0,0 +1,58 @@
use std::cmp::Ordering;
pub use self::{csharp::sort as csharp, osu_legacy::sort as osu_legacy, tandem::TandemSorter};
mod csharp;
mod osu_legacy;
mod tandem;
fn heap_sort<T, F>(keys: &mut [T], lo: usize, hi: usize, cmp: &F)
where
F: Fn(&T, &T) -> Ordering,
{
let n = hi - lo + 1;
for i in (1..=n / 2).rev() {
down_heap(keys, i, n, lo, cmp);
}
for i in (2..=n).rev() {
swap(keys, lo, lo + i - 1);
down_heap(keys, 1, i - 1, lo, cmp);
}
}
fn down_heap<T, F>(keys: &mut [T], mut i: usize, n: usize, lo: usize, cmp: &F)
where
F: Fn(&T, &T) -> Ordering,
{
while i <= n / 2 {
let mut child = 2 * i;
if child < n && cmp(&keys[lo + child - 1], &keys[lo + child]).is_lt() {
child += 1;
}
if cmp(&keys[lo + i - 1], &keys[lo + child - 1]).is_ge() {
break;
}
keys.swap(lo + i - 1, lo + child - 1);
i = child;
}
}
fn swap_if_greater<T, F>(keys: &mut [T], cmp: &F, a: usize, b: usize)
where
F: Fn(&T, &T) -> Ordering,
{
if a != b && cmp(&keys[a], &keys[b]).is_gt() {
keys.swap(a, b);
}
}
fn swap<T>(keys: &mut [T], i: usize, j: usize) {
if i != j {
keys.swap(i, j)
}
}
@@ -4,8 +4,10 @@ use crate::model::hit_object::HitObject;
const QUICK_SORT_DEPTH_THRESHOLD: usize = 32;
/// [Legacy sorting algorithm](https://github.com/ppy/osu/blob/e669e28dc9b6d79d82a36053e6a279de8dafddd1/osu.Game.Rulesets.Mania/MathUtils/LegacySortHelper.cs#L19)
pub fn legacy_sort(keys: &mut [HitObject]) {
/// osu!'s legacy sorting algorithm.
///
/// <https://github.com/ppy/osu/blob/e669e28dc9b6d79d82a36053e6a279de8dafddd1/osu.Game.Rulesets.Mania/MathUtils/LegacySortHelper.cs#L19>
pub fn sort(keys: &mut [HitObject]) {
if keys.len() < 2 {
return;
}
@@ -21,7 +23,7 @@ fn depth_limited_quick_sort(
) {
loop {
if depth_limit == 0 {
heap_sort(keys, left, right);
super::heap_sort(keys, left, right, &cmp);
return;
}
@@ -31,17 +33,9 @@ fn depth_limited_quick_sort(
let mid = i + ((j - i) >> 1);
if keys[i] > keys[mid] {
keys.swap(i, mid);
}
if keys[i] > keys[j] {
keys.swap(i, j);
}
if keys[mid] > keys[j] {
keys.swap(mid, j);
}
super::swap_if_greater(keys, &cmp, i, mid);
super::swap_if_greater(keys, &cmp, i, j);
super::swap_if_greater(keys, &cmp, mid, j);
loop {
while keys[i] < keys[mid] {
@@ -88,32 +82,6 @@ fn depth_limited_quick_sort(
}
}
fn heap_sort(keys: &mut [HitObject], lo: usize, hi: usize) {
let n = hi - lo + 1;
for i in (1..=n / 2).rev() {
down_heap(keys, i, n, lo);
}
for i in (2..=n).rev() {
keys.swap(lo, lo + i - 1);
down_heap(keys, 1, i - 1, lo);
}
}
fn down_heap(keys: &mut [HitObject], mut i: usize, n: usize, lo: usize) {
while i <= n / 2 {
let mut child = 2 * i;
if child < n && keys[lo + child - 1] < keys[lo + child] {
child += 1;
}
if keys[lo + i - 1] >= keys[lo + child - 1] {
break;
}
keys.swap(lo + i - 1, lo + child - 1);
i = child;
}
fn cmp(a: &HitObject, b: &HitObject) -> Ordering {
a.start_time.total_cmp(&b.start_time)
}
@@ -18,6 +18,8 @@ impl TandemSorter {
if stable {
indices.sort_by(sort_by);
} else {
// When sorting integers, the order of elements with equal values
// does not matter so we can use rust's sort instead of C#'s.
indices.sort_unstable_by(sort_by);
}