initial rewrite

This commit is contained in:
MaxOhn
2024-02-09 17:54:14 +01:00
parent ecf1b2c7f4
commit a8bf9601b6
190 changed files with 10892 additions and 18142 deletions
-54
View File
@@ -1,54 +0,0 @@
use std::hash::{BuildHasher, Hasher};
#[derive(Copy, Clone, Default)]
pub(crate) struct ByteHasher;
impl BuildHasher for ByteHasher {
type Hasher = ByteHash;
#[inline]
fn build_hasher(&self) -> Self::Hasher {
ByteHash { byte: 0 }
}
}
pub(crate) struct ByteHash {
byte: u8,
}
impl Hasher for ByteHash {
#[inline]
fn finish(&self) -> u64 {
u64::from(self.byte)
}
#[inline]
fn write(&mut self, _: &[u8]) {
unimplemented!()
}
#[inline]
fn write_u8(&mut self, byte: u8) {
self.byte = byte;
}
}
#[cfg(test)]
mod tests {
use std::hash::{BuildHasher, Hash};
use super::ByteHasher;
#[test]
fn hashes_byte() {
let mut state = ByteHasher.build_hasher();
42_u8.hash(&mut state);
}
#[test]
#[should_panic]
fn doesnt_hash_int() {
let mut state = ByteHasher.build_hasher();
42_i32.hash(&mut state);
}
}
-161
View File
@@ -1,161 +0,0 @@
use std::{
iter::{self, Copied},
slice,
};
#[derive(Clone, Debug, Default)]
pub(crate) struct CompactVec {
inner: Vec<Entry>,
len: usize,
}
impl CompactVec {
const ACCEPTABLE_DIFFERENCE: f64 = 1e-16;
pub(crate) fn new() -> Self {
Self::default()
}
pub(crate) fn push(&mut self, num: f64) {
self.push_n(num, 1);
}
pub(crate) fn push_n(&mut self, num: f64, n: usize) {
if let Some(last) = self
.inner
.last_mut()
.filter(|entry| (entry.value - num).abs() <= Self::ACCEPTABLE_DIFFERENCE)
{
last.count += n;
} else if n > 0 {
self.inner.push(Entry::new(num, n));
}
self.len += n;
}
pub(crate) fn retain<F>(&mut self, mut f: F)
where
F: FnMut(f64) -> bool,
{
self.inner.retain(|entry| f(entry.value));
}
pub(crate) fn iter(&self) -> Iter<'_> {
Iter::new(self)
}
pub(crate) fn to_vec(&self) -> Vec<f64> {
let mut nums = Vec::with_capacity(self.len);
for entry in self.inner.iter() {
nums.extend(iter::repeat(entry.value).take(entry.count));
}
nums
}
pub(crate) fn sum(&self) -> f64 {
self.inner
.iter()
.fold(0.0, |sum, entry| sum + entry.value * entry.count as f64)
}
}
#[derive(Copy, Clone, Debug)]
struct Entry {
value: f64,
count: usize,
}
impl Entry {
const fn new(value: f64, count: usize) -> Self {
Self { value, count }
}
}
pub(crate) struct Iter<'a> {
iter: Copied<slice::Iter<'a, Entry>>,
curr: Option<Entry>,
}
impl<'a> Iter<'a> {
fn new(vec: &'a CompactVec) -> Self {
let mut iter = vec.inner.iter().copied();
Self {
curr: iter.next(),
iter,
}
}
}
impl Iterator for Iter<'_> {
type Item = f64;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
loop {
let curr = self.curr.as_mut()?;
if curr.count == 0 {
self.curr = self.iter.next();
} else {
curr.count -= 1;
return Some(curr.value);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn compact_vec() -> CompactVec {
let mut vec = CompactVec::default();
vec.push_n(123.456, 3);
vec.push(0.0);
vec.push_n(0.0, 2);
vec.push(2.0);
vec.push_n(3.0, 2);
vec
}
#[test]
fn test_len() {
let vec = compact_vec();
assert_eq!(vec.inner.len(), 4);
assert_eq!(vec.len, 9);
}
#[test]
fn test_to_vec() {
let to_vec = compact_vec().to_vec();
assert_eq!(
to_vec,
vec![123.456, 123.456, 123.456, 0.0, 0.0, 0.0, 2.0, 3.0, 3.0]
);
}
#[test]
fn test_iter() {
let vec = compact_vec();
let mut iter = vec.iter();
assert_eq!(iter.next(), Some(123.456));
assert_eq!(iter.next(), Some(123.456));
assert_eq!(iter.next(), Some(123.456));
assert_eq!(iter.next(), Some(0.0));
assert_eq!(iter.next(), Some(0.0));
assert_eq!(iter.next(), Some(0.0));
assert_eq!(iter.next(), Some(2.0));
assert_eq!(iter.next(), Some(3.0));
assert_eq!(iter.next(), Some(3.0));
assert_eq!(iter.next(), None);
}
}
+28 -19
View File
@@ -1,33 +1,42 @@
pub(crate) trait FloatExt: Sized {
pub trait FloatExt: Sized {
// Workaround since rust rounds ties away from 0.0
// while C# rounds them to the nearest even integer.
// See github
// - https://github.com/rust-lang/rust/issues/96710
// - https://github.com/rust-lang/rust/pull/82273
fn round_even(self) -> Self;
/// `self == other`
fn eq(self, other: Self) -> bool;
/// `self != other`
fn not_eq(self, other: Self) -> bool;
}
impl FloatExt for f32 {
#[inline]
fn round_even(self) -> Self {
if (0.5 - self.fract().abs()).abs() <= f32::EPSILON {
2.0 * (self / 2.0).round()
} else {
self.round()
macro_rules! impl_float_ext {
( $ty:ty ) => {
impl FloatExt for $ty {
fn round_even(self) -> Self {
if self.fract().abs().eq(0.5) {
2.0 * (self / 2.0).round()
} else {
self.round()
}
}
fn eq(self, other: Self) -> bool {
(self - other).abs() < <$ty>::EPSILON
}
fn not_eq(self, other: Self) -> bool {
(self - other).abs() >= <$ty>::EPSILON
}
}
}
};
}
impl FloatExt for f64 {
#[inline]
fn round_even(self) -> Self {
if (0.5 - self.fract().abs()).abs() <= f64::EPSILON {
2.0 * (self / 2.0).round()
} else {
self.round()
}
}
}
impl_float_ext!(f32);
impl_float_ext!(f64);
#[cfg(test)]
mod tests {
+119
View File
@@ -0,0 +1,119 @@
use std::cmp::Ordering;
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]) {
if keys.len() < 2 {
return;
}
depth_limited_quick_sort(keys, 0, keys.len() - 1, QUICK_SORT_DEPTH_THRESHOLD);
}
fn depth_limited_quick_sort(
keys: &mut [HitObject],
mut left: usize,
mut right: usize,
mut depth_limit: usize,
) {
loop {
if depth_limit == 0 {
heap_sort(keys, left, right);
return;
}
let mut i = left;
let mut j = right;
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);
}
loop {
while keys[i] < keys[mid] {
i += 1;
}
while keys[mid] < keys[j] {
j -= 1;
}
match i.cmp(&j) {
Ordering::Less => keys.swap(i, j),
Ordering::Equal => {}
Ordering::Greater => break,
}
i += 1;
j = j.saturating_sub(1);
if i > j {
break;
}
}
depth_limit -= 1;
if j.saturating_sub(left) <= right - i {
if left < j {
depth_limited_quick_sort(keys, left, j, depth_limit);
}
left = i;
} else {
if i < right {
depth_limited_quick_sort(keys, i, right, depth_limit);
}
right = j;
}
if left >= right {
break;
}
}
}
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;
}
}
+154 -34
View File
@@ -1,26 +1,21 @@
use std::{
iter::{Cycle, Skip, Take},
ops::Index,
slice::Iter,
};
use std::{mem, ops::Index, slice::Iter};
/// Efficient counterpart to osu!'s [`LimitedCapacityQueue`]
/// i.e. an indexed queue with limited capacity.
/// Efficient counterpart to osu!'s [`LimitedCapacityQueue`] i.e. an indexed
/// queue with limited capacity.
///
/// [`LimitedQueue`] will use an internal array as queue which
/// is stored on the stack. Hence, if the size is very large,
/// e.g. `size_of<T>() * N`, consider using a different type
/// since heap allocation might be favorable.
/// [`LimitedQueue`] will use an internal array as queue which is stored on
/// the stack. Hence, if `size_of<T>() * N` is very large, consider using a
/// different type since heap allocation might be favorable.
///
/// [`LimitedCapacityQueue`]: https://github.com/ppy/osu/blob/b49a1aab8ac6e16e48dffd03f55635cdc1771adf/osu.Game/Rulesets/Difficulty/Utils/LimitedCapacityQueue.cs
#[derive(Clone, Debug)]
pub(crate) struct LimitedQueue<T, const N: usize> {
pub struct LimitedQueue<T, const N: usize> {
queue: [T; N],
/// If the queue is not empty, `end` is the index of the last element.
/// Otherwise, it has no meaning.
end: usize,
/// Amount of elements in the queue. This is equal to `end + 1`
/// if the queue is not full, or `N` otherwise.
/// Amount of elements in the queue. This is equal to `end + 1` if the
/// queue is not full, or `N` otherwise.
len: usize,
}
@@ -28,7 +23,6 @@ impl<T, const N: usize> Default for LimitedQueue<T, N>
where
T: Copy + Clone + Default,
{
#[inline]
fn default() -> Self {
Self {
end: N - 1,
@@ -42,27 +36,33 @@ impl<T, const N: usize> LimitedQueue<T, N>
where
T: Copy + Clone + Default,
{
pub(crate) fn new() -> Self {
pub fn new() -> Self {
Self::default()
}
}
impl<T, const N: usize> LimitedQueue<T, N> {
pub(crate) fn push(&mut self, elem: T) {
pub fn push(&mut self, elem: T) {
self.end = (self.end + 1) % N;
self.queue[self.end] = elem;
self.len += usize::from(self.len < N);
}
pub(crate) const fn is_empty(&self) -> bool {
#[cfg(test)]
pub const fn is_empty(&self) -> bool {
self.len == 0
}
pub(crate) const fn len(&self) -> usize {
pub const fn is_full(&self) -> bool {
self.len == N
}
pub const fn len(&self) -> usize {
self.len
}
pub(crate) const fn last(&self) -> Option<&T> {
#[cfg(test)]
pub const fn last(&self) -> Option<&T> {
if self.is_empty() {
None
} else {
@@ -70,28 +70,85 @@ impl<T, const N: usize> LimitedQueue<T, N> {
}
}
pub(crate) fn iter(&self) -> LimitedQueueIter<'_, T> {
self.queue
.iter()
.cycle()
.skip(usize::from(self.len == N) * (self.end + 1))
.take(self.len)
pub fn as_slices(&self) -> (&[T], &[T]) {
if self.is_full() {
(&self.queue[self.end + 1..N], &self.queue[0..=self.end])
} else {
(&[], &self.queue[0..self.len])
}
}
pub fn iter(&self) -> LimitedQueueIter<'_, T> {
let (head, tail) = self.as_slices();
LimitedQueueIter {
head: head.iter(),
tail: tail.iter(),
}
}
}
pub(crate) type LimitedQueueIter<'a, T> = Take<Skip<Cycle<Iter<'a, T>>>>;
pub struct LimitedQueueIter<'a, T> {
head: Iter<'a, T>,
tail: Iter<'a, T>,
}
impl<'a, T> Iterator for LimitedQueueIter<'a, T> {
type Item = &'a T;
fn next(&mut self) -> Option<Self::Item> {
if let val @ Some(_) = self.head.next() {
val
} else {
mem::swap(&mut self.head, &mut self.tail);
self.head.next()
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
let len = self.len();
(len, Some(len))
}
fn last(mut self) -> Option<Self::Item> {
self.next_back()
}
fn nth(&mut self, mut n: usize) -> Option<Self::Item> {
if self.head.len() <= n {
n -= self.head.len();
mem::swap(&mut self.head, &mut self.tail);
self.tail = [].iter();
}
self.head.nth(n)
}
}
impl<T> DoubleEndedIterator for LimitedQueueIter<'_, T> {
fn next_back(&mut self) -> Option<Self::Item> {
if let val @ Some(_) = self.tail.next_back() {
val
} else {
mem::swap(&mut self.head, &mut self.tail);
self.tail.next_back()
}
}
}
impl<T> ExactSizeIterator for LimitedQueueIter<'_, T> {
fn len(&self) -> usize {
self.head.len() + self.tail.len()
}
}
impl<T, const N: usize> Index<usize> for LimitedQueue<T, N> {
type Output = T;
#[inline]
fn index(&self, idx: usize) -> &Self::Output {
assert!(
idx < self.len,
"index out of bounds: the len is {} but the index is {idx}",
self.len
);
let idx = (idx + usize::from(self.len == N) * (self.end + 1)) % N;
&self.queue[idx]
@@ -136,4 +193,67 @@ mod test {
assert_eq!(queue[0], 2);
assert_eq!(queue[3], 5);
}
#[test]
fn as_slices() {
let mut queue = LimitedQueue::<u8, 3>::default();
assert_eq!(queue.as_slices(), ([].as_slice(), [].as_slice()));
// Start by filling the tail slice
queue.push(1);
assert_eq!(queue.as_slices(), ([].as_slice(), [1].as_slice()));
queue.push(2);
assert_eq!(queue.as_slices(), ([].as_slice(), [1, 2].as_slice()));
queue.push(3);
assert_eq!(queue.as_slices(), ([].as_slice(), [1, 2, 3].as_slice()));
// The buffer is full and wraps around so now it uses the head slice
queue.push(4);
assert_eq!(queue.as_slices(), ([2, 3].as_slice(), [4].as_slice()));
queue.push(5);
assert_eq!(queue.as_slices(), ([3].as_slice(), [4, 5].as_slice()));
queue.push(6);
assert_eq!(queue.as_slices(), ([].as_slice(), [4, 5, 6].as_slice()));
queue.push(7);
assert_eq!(queue.as_slices(), ([5, 6].as_slice(), [7].as_slice()));
}
#[test]
fn iter_nth() {
const CAPACITY: usize = 5;
const LIMIT: usize = 11;
let mut queue = LimitedQueue::<u8, CAPACITY>::default();
// The queue is not yet full
for n in 0..CAPACITY {
queue.push(n as u8);
for i in 0..=n {
assert_eq!(queue.iter().nth(i), Some(&(i as u8)));
}
for i in n + 1..CAPACITY {
assert_eq!(queue.iter().nth(i), None);
}
}
// The queue is full
for n in CAPACITY..LIMIT {
queue.push(n as u8);
let (head, tail) = queue.as_slices();
for i in 0..head.len() {
assert_eq!(queue.iter().nth(i), Some(&head[i]));
}
for i in 0..tail.len() {
assert_eq!(queue.iter().nth(head.len() + i), Some(&tail[i]));
}
assert_eq!(queue.iter().nth(head.len() + tail.len() + 1), None);
}
}
}
+72
View File
@@ -0,0 +1,72 @@
use std::fmt::{Debug, Formatter, Result as FmtResult};
use crate::model::{beatmap::Converted, mode::IGameMode};
pub enum MapOrAttrs<'map, M: IGameMode> {
Map(Converted<'map, M>),
Attrs(M::DifficultyAttributes),
}
impl<'map, M: IGameMode> MapOrAttrs<'map, M> {
/// Return a mutable reference to the attributes.
///
/// If `self` is of variant `Map`, store `attrs` in `self`, and return a
/// mutable reference to it.
pub fn attrs_or_insert(
&mut self,
attrs: M::DifficultyAttributes,
) -> &mut M::DifficultyAttributes {
match self {
MapOrAttrs::Map(_) => {
*self = Self::Attrs(attrs);
let Self::Attrs(ref mut other) = self else {
unreachable!()
};
other
}
MapOrAttrs::Attrs(ref mut other) => other,
}
}
}
impl<M> Clone for MapOrAttrs<'_, M>
where
M: IGameMode,
M::DifficultyAttributes: Clone,
{
fn clone(&self) -> Self {
match self {
Self::Map(converted) => Self::Map(converted.clone()),
Self::Attrs(attrs) => Self::Attrs(attrs.clone()),
}
}
}
impl<M> Debug for MapOrAttrs<'_, M>
where
M: IGameMode,
M::DifficultyAttributes: Debug,
{
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
match self {
Self::Map(converted) => f.debug_tuple("Map").field(converted).finish(),
Self::Attrs(attrs) => f.debug_tuple("Attrs").field(attrs).finish(),
}
}
}
impl<M> PartialEq for MapOrAttrs<'_, M>
where
M: IGameMode,
M::DifficultyAttributes: PartialEq,
{
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::Map(a), Self::Map(b)) => a == b,
(Self::Attrs(a), Self::Attrs(b)) => a == b,
_ => false,
}
}
}
-50
View File
@@ -1,50 +0,0 @@
use crate::Beatmap;
#[derive(Clone, Debug)]
pub(crate) enum MapOrElse<M, E> {
Map(M),
Else(E),
}
impl<M, E> MapOrElse<M, E> {
/// Return a mutable reference to `Else`.
///
/// If `self` is of variant `Map`, store `other` in `self`, and return a
/// mutable reference to it.
pub(crate) fn else_or_insert(&mut self, other: E) -> &mut E {
match self {
MapOrElse::Map(_) => {
*self = Self::Else(other);
let Self::Else(ref mut other) = self else {
unreachable!()
};
other
}
MapOrElse::Else(ref mut other) => other,
}
}
}
impl<'map, E> From<&'map Beatmap> for MapOrElse<MapRef<'map>, E> {
fn from(map: &'map Beatmap) -> Self {
Self::Map(MapRef(map))
}
}
/// References don't implement [`Deref`] so we implement a wrapper type.
#[derive(Copy, Clone, Debug)]
pub(crate) struct MapRef<'map>(&'map Beatmap);
impl<'map> MapRef<'map> {
pub(crate) const fn into_inner(self) -> &'map Beatmap {
self.0
}
}
impl AsRef<Beatmap> for MapRef<'_> {
fn as_ref(&self) -> &Beatmap {
self.0
}
}
+7 -15
View File
@@ -1,15 +1,7 @@
mod byte_hasher;
mod compact_vec;
mod float_ext;
mod limited_queue;
mod map_or_else;
mod tandem_sort;
pub(crate) use self::{
byte_hasher::ByteHasher,
compact_vec::CompactVec,
float_ext::FloatExt,
limited_queue::LimitedQueue,
map_or_else::{MapOrElse, MapRef},
tandem_sort::TandemSorter,
};
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;
+53
View File
@@ -0,0 +1,53 @@
pub trait Mods: Copy {
fn nf(self) -> bool;
fn ez(self) -> bool;
fn td(self) -> bool;
fn hd(self) -> bool;
fn hr(self) -> bool;
fn dt(self) -> bool;
fn rx(self) -> bool;
fn ht(self) -> bool;
fn fl(self) -> bool;
fn so(self) -> bool;
fn clock_rate(self) -> f64 {
if self.dt() {
1.5
} else if self.ht() {
0.75
} else {
1.0
}
}
fn od_ar_hp_multiplier(self) -> f64 {
if self.hr() {
1.4
} else if self.ez() {
0.5
} else {
1.0
}
}
}
macro_rules! impl_mods_fn {
( $fn_name:ident, $bits:expr ) => {
fn $fn_name(self) -> bool {
self & ($bits) != 0
}
};
}
impl Mods for u32 {
impl_mods_fn!(nf, 1 << 0);
impl_mods_fn!(ez, 1 << 1);
impl_mods_fn!(td, 1 << 2);
impl_mods_fn!(hd, 1 << 3);
impl_mods_fn!(hr, 1 << 4);
impl_mods_fn!(dt, 1 << 6);
impl_mods_fn!(rx, 1 << 7);
impl_mods_fn!(ht, 1 << 8);
impl_mods_fn!(fl, 1 << 10);
impl_mods_fn!(so, 1 << 12);
}
+62
View File
@@ -0,0 +1,62 @@
const INT_TO_REAL: f64 = 1.0 / (i32::MAX as f64 + 1.0);
const INT_MASK: u32 = 0x7F_FF_FF_FF;
pub struct Random {
x: u32,
y: u32,
z: u32,
w: u32,
bit_buf: u32,
bit_idx: i32,
}
impl Random {
pub const fn new(seed: i32) -> Self {
Self {
x: seed as u32,
y: 842_502_087,
z: 3_579_807_591,
w: 273_326_509,
bit_buf: 0,
bit_idx: 32,
}
}
pub fn gen_unsigned(&mut self) -> u32 {
let t = self.x ^ (self.x << 11);
self.x = self.y;
self.y = self.z;
self.z = self.w;
self.w = self.w ^ (self.w >> 19) ^ t ^ (t >> 8);
self.w
}
pub fn next_int(&mut self) -> i32 {
(INT_MASK & self.gen_unsigned()) as i32
}
pub fn next_double(&mut self) -> f64 {
INT_TO_REAL * f64::from(self.next_int())
}
pub fn next_int_range(&mut self, min: i32, max: i32) -> i32 {
(f64::from(min) + self.next_double() * f64::from(max - min)) as i32
}
pub fn next_double_range(&mut self, min: f64, max: f64) -> i32 {
(min + self.next_double() * (max - min)) as i32
}
pub fn next_bool(&mut self) -> bool {
if self.bit_idx == 32 {
self.bit_buf = self.gen_unsigned();
self.bit_idx = 1;
} else {
self.bit_idx += 1;
self.bit_buf >>= 1;
}
(self.bit_buf & 1) == 1
}
}
+25 -25
View File
@@ -1,37 +1,39 @@
use std::cmp::Ordering;
/// Stores the sorted order for an initial list so that multiple
/// lists can be sorted based on that order.
pub(crate) struct TandemSorter {
/// Stores the sorted order for an initial list so that multiple
/// lists can be sorted based on that order.
pub struct TandemSorter {
indices: Box<[usize]>,
should_reset: bool,
}
impl TandemSorter {
/// Sort indices based on the given slice.
///
/// Note that this does **not** sort the given slice.
pub(crate) fn new<T>(slice: &[T], stable: bool) -> Self
where
T: PartialOrd,
{
let mut indices: Box<[usize]> = (0..).take(slice.len()).collect();
let closure =
|&i: &usize, &j: &usize| slice[i].partial_cmp(&slice[j]).unwrap_or(Ordering::Equal);
pub fn new<T>(slice: &[T], cmp: fn(&T, &T) -> Ordering, stable: bool) -> Self {
let mut indices: Box<[usize]> = (0..slice.len()).collect();
let sort_by = |&i: &usize, &j: &usize| cmp(&slice[i], &slice[j]);
if stable {
indices.sort_by(closure);
indices.sort_by(sort_by);
} else {
indices.sort_unstable_by(closure);
indices.sort_unstable_by(sort_by);
}
Self { indices }
Self {
indices,
should_reset: false,
}
}
/// Sort the given slice based on the internal ordering.
///
/// If you intend to sort another slice afterwards,
/// don't forget to call [`Self::toggle_marks`] first.
pub(crate) fn sort<T>(&mut self, slice: &mut [T]) {
pub fn sort<T>(&mut self, slice: &mut [T]) {
if self.should_reset {
self.toggle_marks();
self.should_reset = false;
}
for i in 0..self.indices.len() {
let i_idx = self.indices[i];
@@ -52,22 +54,21 @@ impl TandemSorter {
self.indices[j] = Self::toggle_mark_idx(j_idx);
}
self.should_reset = true;
}
/// This method must be called inbetween sorting slices.
pub(crate) fn toggle_marks(&mut self) {
fn toggle_marks(&mut self) {
for idx in self.indices.iter_mut() {
*idx = Self::toggle_mark_idx(*idx);
}
}
#[inline(always)]
const fn idx_is_marked(idx: usize) -> bool {
// Check if first bit is set
idx.leading_zeros() == 0
}
#[inline(always)]
const fn toggle_mark_idx(idx: usize) -> usize {
// Flip the first bit
idx ^ !(usize::MAX >> 1)
@@ -81,13 +82,12 @@ mod tests {
#[test]
fn sort() {
let mut base = vec![9, 7, 8, 1, 4, 3, 5, 2];
let mut sorter = TandemSorter::new(&base, false);
let mut other = "hello World".chars().collect::<Vec<_>>();
let mut sorter = TandemSorter::new(&base, u8::cmp, false);
sorter.sort(&mut base);
assert_eq!(base, vec![1, 2, 3, 4, 5, 7, 8, 9]);
sorter.toggle_marks();
let mut other = "hello World".chars().collect::<Vec<_>>();
sorter.sort(&mut other);
assert_eq!(other, "lo oWelhrld".chars().collect::<Vec<_>>());
}