use crate::utils::bytes_of;
use std::{
cmp::min,
mem::{MaybeUninit, size_of},
};
const DEFAULT_SMALL_LIMIT: usize = 8;
pub fn advance<T, F>(slice: &[T], function: F) -> usize
where
F: Fn(&T) -> bool,
{
advance_raw::<T, F, DEFAULT_SMALL_LIMIT>(slice, function)
}
pub fn advance_raw<T, F, const SMALL_LIMIT: usize>(slice: &[T], function: F) -> usize
where
F: Fn(&T) -> bool,
{
if slice.len() > SMALL_LIMIT && function(&slice[SMALL_LIMIT]) {
let mut index = SMALL_LIMIT + 1;
if index < slice.len() && function(&slice[index]) {
let mut step = 1;
while index + step < slice.len() && function(&slice[index + step]) {
index += step;
step <<= 1;
}
step >>= 1;
while step > 0 {
if index + step < slice.len() && function(&slice[index + step]) {
index += step;
}
step >>= 1;
}
index += 1;
}
index
} else {
let limit = min(slice.len(), SMALL_LIMIT);
slice[..limit]
.iter()
.position(|x| !function(x))
.unwrap_or(limit)
}
}
pub fn retreat<T, F>(slice: &[T], function: F) -> usize
where
F: Fn(&T) -> bool,
{
retreat_raw::<T, F, DEFAULT_SMALL_LIMIT>(slice, function)
}
pub fn retreat_raw<T, F, const SMALL_LIMIT: usize>(slice: &[T], function: F) -> usize
where
F: Fn(&T) -> bool,
{
if slice.is_empty() {
return 0;
}
let last_index = slice.len() - 1;
if slice.len() > SMALL_LIMIT && function(&slice[last_index - SMALL_LIMIT]) {
let mut index = SMALL_LIMIT + 1;
if index < slice.len() && function(&slice[last_index - index]) {
let mut step = 1;
while index + step < slice.len() && function(&slice[last_index - (index + step)]) {
index += step;
step <<= 1;
}
step >>= 1;
while step > 0 {
if index + step < slice.len() && function(&slice[last_index - (index + step)]) {
index += step;
}
step >>= 1;
}
index += 1;
}
index
} else {
let limit = min(last_index, SMALL_LIMIT);
slice[last_index - limit..]
.iter()
.rev()
.position(|x| !function(x))
.unwrap_or(limit + 1)
}
}
pub fn dyn_advance<T>(slice: &[T], function: &dyn Fn(*const u8) -> bool) -> usize {
advance_erased(bytes_of(slice), size_of::<T>(), function)
}
pub fn advance_erased(
slice: &[MaybeUninit<u8>],
size: usize,
function: &dyn Fn(*const u8) -> bool,
) -> usize {
if size == 0 {
return 0;
}
let slice = SlicePtr::new(slice, size);
unsafe {
if slice.len() > DEFAULT_SMALL_LIMIT && function(slice.get_unchecked(DEFAULT_SMALL_LIMIT)) {
let mut index = DEFAULT_SMALL_LIMIT + 1;
if index < slice.len() && function(slice.get_unchecked(index)) {
let mut step = 1;
while index + step < slice.len() && function(slice.get_unchecked(index + step)) {
index += step;
step <<= 1;
}
step >>= 1;
while step > 0 {
if index + step < slice.len() && function(slice.get_unchecked(index + step)) {
index += step;
}
step >>= 1;
}
index += 1;
}
index
} else {
let limit = min(slice.len(), DEFAULT_SMALL_LIMIT);
for offset in 0..limit {
if !function(slice.get_unchecked(offset)) {
return offset;
}
}
limit
}
}
}
pub fn dyn_retreat<T>(slice: &[T], function: &dyn Fn(*const u8) -> bool) -> usize {
retreat_erased(bytes_of(slice), size_of::<T>(), function)
}
pub fn retreat_erased(
slice: &[MaybeUninit<u8>],
size: usize,
function: &dyn Fn(*const u8) -> bool,
) -> usize {
if size == 0 {
return 0;
}
if slice.is_empty() {
return 0;
}
let slice = SlicePtr::new(slice, size);
let last_index = slice.len() - 1;
unsafe {
if slice.len() > DEFAULT_SMALL_LIMIT
&& function(slice.get_unchecked(last_index - DEFAULT_SMALL_LIMIT))
{
let mut index = DEFAULT_SMALL_LIMIT + 1;
if index < slice.len() && function(slice.get_unchecked(last_index - index)) {
let mut step = 1;
while index + step < slice.len()
&& function(slice.get_unchecked(last_index - (index + step)))
{
index += step;
step <<= 1;
}
step >>= 1;
while step > 0 {
if index + step < slice.len()
&& function(slice.get_unchecked(last_index - (index + step)))
{
index += step;
}
step >>= 1;
}
index += 1;
}
index
} else {
let limit = min(last_index, DEFAULT_SMALL_LIMIT);
for offset in 0..=limit {
if !function(slice.get_unchecked(last_index - offset)) {
return offset;
}
}
limit + 1
}
}
}
struct SlicePtr {
ptr: *const u8,
elements: usize,
element_size: usize,
}
impl SlicePtr {
#[inline]
fn new(slice: &[MaybeUninit<u8>], element_size: usize) -> Self {
debug_assert!(slice.len().is_multiple_of(element_size));
Self {
ptr: slice.as_ptr().cast(),
elements: slice.len() / element_size,
element_size,
}
}
#[inline]
const fn len(&self) -> usize {
self.elements
}
#[inline]
unsafe fn get_unchecked(&self, idx: usize) -> *const u8 {
debug_assert!(idx < self.elements);
unsafe { self.ptr.add(idx * self.element_size) }
}
}
#[cfg(test)]
mod tests {
use crate::utils::{
advance, advance_erased, advance_retreat::DEFAULT_SMALL_LIMIT, bytes_of, retreat,
retreat_erased,
};
use proptest::{
arbitrary::any, collection::vec, prop_assert_eq, proptest, sample::SizeRange,
strategy::Strategy, test_runner::TestCaseResult,
};
use std::mem::{align_of, size_of};
const HALF: usize = usize::MAX / 2;
#[test]
fn advance_empty() {
let haystack = &[false, false, false, false, false];
assert_eq!(advance(haystack, |&x| x), 0);
assert_eq!(retreat(haystack, |&x| x), 0);
let haystack = &[
false, false, false, false, false, false, false, false, false, false,
];
assert_eq!(advance(haystack, |&x| x), 0);
assert_eq!(retreat(haystack, |&x| x), 0);
}
#[test]
fn advance_small() {
let haystack = &[true, true, false, false, false];
assert_eq!(advance(haystack, |&x| x), 2);
assert_eq!(retreat(haystack, |&x| !x), 3);
let haystack = &[
true, true, true, false, false, false, false, false, false, false,
];
assert_eq!(advance(haystack, |&x| x), 3);
assert_eq!(retreat(haystack, |&x| !x), 7);
}
#[test]
fn advance_medium() {
let haystack = &[
true, true, true, true, true, true, true, true, true, true, false, false, false, false,
false, false, false, false, false, false,
];
assert_eq!(advance(haystack, |&x| x), 10);
assert_eq!(retreat(haystack, |&x| !x), 10);
}
#[test]
fn advance_erased_empty() {
let haystack: &[u64] = &[5, 5, 5, 5, 5];
let count = advance_erased(bytes_of(haystack), size_of::<u64>(), &|x| unsafe {
let value = *x.cast::<u64>();
assert_eq!(value, 5);
value == 1
});
assert_eq!(count, 0);
let haystack: &[u64] = &[5, 5, 5, 5, 5, 5, 5, 5, 5, 5];
let count = advance_erased(bytes_of(haystack), size_of::<u64>(), &|x| unsafe {
let value = *x.cast::<u64>();
assert_eq!(value, 5);
value == 1
});
assert_eq!(count, 0);
}
#[test]
fn advance_erased_small() {
let haystack: &[u64] = &[1, 1, 568, 568, 568];
let count = advance_erased(bytes_of(haystack), size_of::<u64>(), &|x| unsafe {
let value = *x.cast::<u64>();
assert!(value == 1 || value == 568);
value == 1
});
assert_eq!(count, 2);
let haystack: &[u64] = &[1, 1, 1, 568, 568, 568, 568, 568, 568, 568];
let count = advance_erased(bytes_of(haystack), size_of::<u64>(), &|x| unsafe {
let value = *x.cast::<u64>();
assert!(value == 1 || value == 568);
value == 1
});
assert_eq!(count, 3);
}
#[test]
fn advance_erased_medium() {
let haystack: &[u64] = &[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 568, 568, 568];
let count = advance_erased(bytes_of(haystack), size_of::<u64>(), &|x| unsafe {
let value = *x.cast::<u64>();
assert!(value == 1 || value == 568);
value == 1
});
assert_eq!(count, 10);
}
fn haystack(
length: impl Into<SizeRange>,
value: impl Strategy<Value = usize>,
) -> impl Strategy<Value = Vec<usize>> {
vec(value, length.into()).prop_map(|mut vec| {
vec.sort();
vec
})
}
fn advance_test(needle: usize, haystack: &[usize]) -> TestCaseResult {
let count = advance(haystack, |&x| x < needle);
let expected = haystack
.iter()
.position(|&x| x >= needle)
.unwrap_or(haystack.len());
prop_assert_eq!(count, expected);
Ok(())
}
fn retreat_test(needle: usize, haystack: &[usize]) -> TestCaseResult {
let count = retreat(haystack, |&x| x > needle);
let expected = haystack
.iter()
.rev()
.position(|&x| x <= needle)
.unwrap_or(haystack.len());
prop_assert_eq!(count, expected);
Ok(())
}
fn advance_erased_test(needle: usize, haystack: &[usize]) -> TestCaseResult {
let count = advance_erased(bytes_of(haystack), size_of::<usize>(), &|x| unsafe {
assert!(
x as usize & (align_of::<usize>() - 1) == 0,
"unaligned pointer",
);
*x.cast::<usize>() < needle
});
let expected = haystack
.iter()
.position(|&x| x >= needle)
.unwrap_or(haystack.len());
prop_assert_eq!(count, expected);
Ok(())
}
fn retreat_erased_test(needle: usize, haystack: &[usize]) -> TestCaseResult {
let count = retreat_erased(bytes_of(haystack), size_of::<usize>(), &|x| unsafe {
assert!(
x as usize & (align_of::<usize>() - 1) == 0,
"unaligned pointer",
);
*x.cast::<usize>() > needle
});
let expected = haystack
.iter()
.rev()
.position(|&x| x <= needle)
.unwrap_or(haystack.len());
prop_assert_eq!(count, expected);
Ok(())
}
proptest! {
#[test]
fn advance_less_than(needle in any::<usize>(), haystack in haystack(0..100_000usize, any::<usize>())) {
advance_test(needle, &haystack)?;
}
#[test]
fn retreat_less_than(needle in any::<usize>(), haystack in haystack(0..100_000usize, any::<usize>())) {
retreat_test(needle, &haystack)?;
}
#[test]
fn advance_less_than_unsat1(needle in ..HALF, haystack in haystack(0..100_000usize, HALF..)) {
advance_test(needle, &haystack)?;
}
#[test]
fn advance_less_than_unsat2(needle in HALF.., haystack in haystack(0..100_000usize, ..HALF)) {
advance_test(needle, &haystack)?;
}
#[test]
fn retreat_less_than_unsat1(needle in ..HALF, haystack in haystack(0..100_000usize, HALF..)) {
retreat_test(needle, &haystack)?;
}
#[test]
fn retreat_less_than_unsat2(needle in HALF.., haystack in haystack(0..100_000usize, ..HALF)) {
retreat_test(needle, &haystack)?;
}
#[test]
fn advance_less_than_small(needle in any::<usize>(), haystack in haystack(0..=DEFAULT_SMALL_LIMIT, any::<usize>())) {
advance_test(needle, &haystack)?;
}
#[test]
fn retreat_less_than_small(needle in any::<usize>(), haystack in haystack(0..=DEFAULT_SMALL_LIMIT, any::<usize>())) {
retreat_test(needle, &haystack)?;
}
#[test]
fn advance_less_than_small_unsat1(needle in ..HALF, haystack in haystack(0..=DEFAULT_SMALL_LIMIT, HALF..)) {
advance_test(needle, &haystack)?;
}
#[test]
fn advance_less_than_small_unsat2(needle in HALF.., haystack in haystack(0..=DEFAULT_SMALL_LIMIT, ..HALF)) {
advance_test(needle, &haystack)?;
}
#[test]
fn retreat_less_than_small_unsat1(needle in ..HALF, haystack in haystack(0..=DEFAULT_SMALL_LIMIT, HALF..)) {
retreat_test(needle, &haystack)?;
}
#[test]
fn retreat_less_than_small_unsat2(needle in HALF.., haystack in haystack(0..=DEFAULT_SMALL_LIMIT, ..HALF)) {
retreat_test(needle, &haystack)?;
}
#[test]
fn advance_erased_less_than(needle in any::<usize>(), haystack in haystack(0..100_000usize, any::<usize>())) {
advance_erased_test(needle, &haystack)?;
}
#[test]
fn advance_erased_less_than_unsat(needle in ..HALF, haystack in haystack(0..100_000usize, HALF..)) {
advance_erased_test(needle, &haystack)?;
}
#[test]
fn advance_erased_less_than_small(needle in any::<usize>(), haystack in haystack(0..=DEFAULT_SMALL_LIMIT, any::<usize>())) {
advance_erased_test(needle, &haystack)?;
}
#[test]
fn advance_erased_less_than_small_unsat(needle in ..HALF, haystack in haystack(0..=DEFAULT_SMALL_LIMIT, HALF..)) {
advance_erased_test(needle, &haystack)?;
}
#[test]
fn retreat_erased_less_than(needle in any::<usize>(), haystack in haystack(0..100_000usize, any::<usize>())) {
retreat_erased_test(needle, &haystack)?;
}
#[test]
fn retreat_erased_less_than_unsat1(needle in HALF.., haystack in haystack(0..100_000usize, ..HALF)) {
retreat_erased_test(needle, &haystack)?;
}
#[test]
fn retreat_erased_less_than_unsat2(needle in ..HALF, haystack in haystack(0..100_000usize, HALF..)) {
retreat_erased_test(needle, &haystack)?;
}
#[test]
fn retreat_erased_less_than_small(needle in any::<usize>(), haystack in haystack(0..=DEFAULT_SMALL_LIMIT, any::<usize>())) {
retreat_erased_test(needle, &haystack)?;
}
#[test]
fn retreat_erased_less_than_small_unsat(needle in HALF.., haystack in haystack(0..=DEFAULT_SMALL_LIMIT, ..HALF)) {
advance_erased_test(needle, &haystack)?;
}
}
}