Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Eq implemented #13

Merged
merged 2 commits into from
Dec 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## 0.3.0
### Added
- `BitSetInterface::is_empty`.
- All `BitSetInterface`s now implement `Eq`.
- `BitSet` (without &) now implements op's too.

## 0.2.0
### Changed
- `IConfig` renamed to `Config`.
Expand Down
2 changes: 2 additions & 0 deletions src/bit_block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use crate::bit_queue::{ArrayBitQueue, BitQueue, PrimitiveBitQueue};
// TODO: consider removing copy
pub trait BitBlock
: BitAnd<Output = Self> + BitOr<Output = Self> + BitXor<Output = Self>
+ Eq + PartialEq
+ Sized + Copy + Clone
{
const SIZE_POT_EXPONENT: usize;
Expand All @@ -17,6 +18,7 @@ pub trait BitBlock

fn get_bit(&self, bit_index: usize) -> bool;

/// Returns Break if traverse was breaked.
fn traverse_bits<F>(&self, f: F) -> ControlFlow<()>
where
F: FnMut(usize) -> ControlFlow<()>;
Expand Down
215 changes: 182 additions & 33 deletions src/bitset_interface.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use std::mem::{ManuallyDrop, MaybeUninit};
use std::ops::ControlFlow;
use crate::{BitSet, level_indices};
use crate::binary_op::BinaryOp;
use crate::bit_block::BitBlock;
Expand Down Expand Up @@ -147,6 +148,28 @@ impl<'a, T: LevelMasksExt> LevelMasksExt for &'a T {
}
}

/// Helper function
///
/// # Safety
///
/// Only safe to call if you iterate `set`.
/// (`set` at the top of lazy bitset operations hierarchy)
#[inline]
pub(crate) unsafe fn iter_update_level1_blocks<S: LevelMasksExt>(
set: &S,
cache_data: &mut S::CacheData,
level1_blocks: &mut MaybeUninit<S::Level1Blocks>,
level0_index: usize
) -> <S::Conf as Config>::Level1BitBlock{
let (level1_mask, valid) = unsafe {
set.update_level1_blocks(cache_data, level1_blocks, level0_index)
};
if !valid {
// level1_mask can not be empty here
unsafe { std::hint::unreachable_unchecked() }
}
level1_mask
}

// User-side interface
pub trait BitSetInterface: BitSetBase + IntoIterator<Item = usize> + LevelMasksExt {
Expand All @@ -160,6 +183,8 @@ pub trait BitSetInterface: BitSetBase + IntoIterator<Item = usize> + LevelMasksE
fn into_block_iter(self) -> Self::IntoBlockIter;

fn contains(&self, index: usize) -> bool;

fn is_empty(&self) -> bool;
}

impl<T: LevelMasksExt> BitSetInterface for T
Expand Down Expand Up @@ -195,8 +220,163 @@ where
data_block.get_bit(data_index)
}
}

#[inline]
fn is_empty(&self) -> bool {
self.level0_mask().is_zero()
}
}

macro_rules! impl_all {
($macro_name: ident) => {
$macro_name!(impl<Conf> for BitSet<Conf> where Conf: Config);
$macro_name!(
impl<Op, S1, S2> for BitSetOp<Op, S1, S2>
where
Op: BinaryOp,
S1: LevelMasksExt<Conf = S2::Conf>,
S2: LevelMasksExt
);
$macro_name!(
impl<Op, S, Storage> for Reduce<Op, S, Storage>
where
Op: BinaryOp,
S: Iterator + Clone,
S::Item: LevelMasksExt,
Storage: ReduceCache
);
}
}

macro_rules! impl_all_ref {
($macro_name: ident) => {
$macro_name!(impl<'a, Conf> for &'a BitSet<Conf> where Conf: Config);
$macro_name!(
impl<'a, Op, S1, S2> for &'a BitSetOp<Op, S1, S2>
where
Op: BinaryOp,
S1: LevelMasksExt<Conf = S2::Conf>,
S2: LevelMasksExt
);
$macro_name!(
impl<'a, Op, S, Storage> for &'a Reduce<Op, S, Storage>
where
Op: BinaryOp,
S: Iterator + Clone,
S::Item: LevelMasksExt,
Storage: ReduceCache
);
}
}


// TODO: remove
/*// Optimistic in-depth check.
fn bitsets_eq_simple<L, R>(left: L, right: R) -> bool
where
L: LevelMasks,
R: LevelMasks<Conf = L::Conf>,
{
let left_level0_mask = left.level0_mask();
let right_level0_mask = right.level0_mask();

if left_level0_mask != right_level0_mask {
return false;
}

use ControlFlow::*;
left_level0_mask.traverse_bits(|level0_index|{
let left_level1_mask = unsafe{ left.level1_mask(level0_index) };
let right_level1_mask = unsafe{ right.level1_mask(level0_index) };

if left_level1_mask != right_level1_mask {
return Break(());
}

// simple-iter like
// TODO: change to cache iter -like
left_level1_mask.traverse_bits(|level1_index|{
let left_data = unsafe{ left.data_mask(level0_index, level1_index) };
let right_data = unsafe{ right.data_mask(level0_index, level1_index) };

if left_data == right_data{
Continue(())
} else {
Break(())
}
})
}).is_continue()
}*/

// Optimistic depth-first check.
fn bitsets_eq<L, R>(left: L, right: R) -> bool
where
L: LevelMasksExt,
R: LevelMasksExt<Conf = L::Conf>,
{
let left_level0_mask = left.level0_mask();
let right_level0_mask = right.level0_mask();

if left_level0_mask != right_level0_mask {
return false;
}

let mut left_cache_data = left.make_cache();
let mut right_cache_data = right.make_cache();

let mut left_level1_blocks = MaybeUninit::uninit();
let mut right_level1_blocks = MaybeUninit::uninit();

use ControlFlow::*;
left_level0_mask.traverse_bits(|level0_index|{
let left_level1_mask = unsafe {
iter_update_level1_blocks(&left, &mut left_cache_data, &mut left_level1_blocks, level0_index)
};
let right_level1_mask = unsafe {
iter_update_level1_blocks(&right, &mut right_cache_data, &mut right_level1_blocks, level0_index)
};

if left_level1_mask != right_level1_mask {
return Break(());
}

left_level1_mask.traverse_bits(|level1_index|{
let left_data = unsafe {
L::data_mask_from_blocks(left_level1_blocks.assume_init_ref(), level1_index)
};
let right_data = unsafe {
R::data_mask_from_blocks(right_level1_blocks.assume_init_ref(), level1_index)
};

if left_data == right_data{
Continue(())
} else {
Break(())
}
})
}).is_continue()
}

macro_rules! impl_eq {
(impl <$($generics:tt),*> for $t:ty where $($where_bounds:tt)*) => {
impl<$($generics),*,Rhs> PartialEq<Rhs> for $t
where
$($where_bounds)*,
Rhs: BitSetInterface<Conf = <Self as BitSetBase>::Conf>
{
#[inline]
fn eq(&self, other: &Rhs) -> bool {
bitsets_eq(self, other)
}
}

impl<$($generics),*> Eq for $t
where
$($where_bounds)*
{}
}
}
impl_all!(impl_eq);

macro_rules! impl_into_iter {
(impl <$($generics:tt),*> for $t:ty where $($where_bounds:tt)*) => {
Expand All @@ -214,36 +394,5 @@ macro_rules! impl_into_iter {
}
};
}

impl_into_iter!(impl<Conf> for BitSet<Conf> where Conf: Config );
impl_into_iter!(impl<'a, Conf> for &'a BitSet<Conf> where Conf: Config );
impl_into_iter!(
impl<Op, S1, S2> for BitSetOp<Op, S1, S2>
where
Op: BinaryOp,
S1: LevelMasksExt<Conf = S2::Conf>,
S2: LevelMasksExt
);
impl_into_iter!(
impl<'a, Op, S1, S2> for &'a BitSetOp<Op, S1, S2>
where
Op: BinaryOp,
S1: LevelMasksExt<Conf = S2::Conf>,
S2: LevelMasksExt
);
impl_into_iter!(
impl<Op, S, Storage> for Reduce<Op, S, Storage>
where
Op: BinaryOp,
S: Iterator + Clone,
S::Item: LevelMasksExt,
Storage: ReduceCache
);
impl_into_iter!(
impl<'a, Op, S, Storage> for &'a Reduce<Op, S, Storage>
where
Op: BinaryOp,
S: Iterator + Clone,
S::Item: LevelMasksExt,
Storage: ReduceCache
);
impl_all!(impl_into_iter);
impl_all_ref!(impl_into_iter);
1 change: 1 addition & 0 deletions src/bitset_op.rs
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ macro_rules! impl_op {
};
}

impl_op!(impl<Conf> for BitSet<Conf> where Conf: Config);
impl_op!(impl<'a, Conf> for &'a BitSet<Conf> where Conf: Config);
impl_op!(impl<Op, S1, S2> for BitSetOp<Op, S1, S2> where /* S1: BitSetInterface, S2: BitSetInterface */);
impl_op!(impl<'a, Op, S1, S2> for &'a BitSetOp<Op, S1, S2> where /* S1: BitSetInterface, S2: BitSetInterface */);
Expand Down
19 changes: 6 additions & 13 deletions src/iter/caching.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::mem::{ManuallyDrop, MaybeUninit};

use crate::bit_block::BitBlock;
use crate::bit_queue::BitQueue;
use crate::bitset_interface::{BitSetBase, LevelMasksExt};
use crate::bitset_interface::{BitSetBase, iter_update_level1_blocks, LevelMasksExt};
use crate::level_indices;

use super::*;
Expand Down Expand Up @@ -103,13 +103,9 @@ where
self.state.level0_index = level0_index;

// generate level1 mask, and update cache.
let (level1_mask, valid) = unsafe {
self.virtual_set.update_level1_blocks(&mut self.cache_data, &mut self.level1_blocks, level0_index)
let level1_mask = unsafe {
iter_update_level1_blocks(&self.virtual_set, &mut self.cache_data, &mut self.level1_blocks, level0_index)
};
if !valid {
// level1_mask can not be empty here
unsafe { std::hint::unreachable_unchecked() }
}
self.state.level1_iter = level1_mask.bits_iter();

// TODO: can we mask SIMD block directly?
Expand Down Expand Up @@ -147,13 +143,10 @@ where
if let Some(index) = state.level0_iter.next() {
state.level0_index = index;

let (level1_mask, valid) = unsafe {
virtual_set.update_level1_blocks(cache_data, level1_blocks, index)
let level1_mask = unsafe{
iter_update_level1_blocks(virtual_set, cache_data, level1_blocks, index)
};
if !valid {
// level1_mask can not be empty here
unsafe { std::hint::unreachable_unchecked() }
}

state.level1_iter = level1_mask.bits_iter();
} else {
return None;
Expand Down
7 changes: 7 additions & 0 deletions src/iter/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,19 @@ pub(crate) struct State<Conf: Config> {

/// Block iterator.
///
/// # Empty blocks
///
/// Block iterator may occasionally return empty blocks.
/// This is for performance reasons - since you most likely will
/// traverse block indices in loop anyway - checking it for emptiness, and then looping to the
/// next non-empty one inside BlockIterator - may be just unnecessary operation.
///
/// [BitSet] and intersection operations are guaranteed to never return empty blocks
/// during iteration.
///
/// TODO: consider changing this behavior.
///
/// [BitSet]: crate::BitSet
pub trait BlockIterator
: Iterator<Item = DataBlock<Self::DataBitBlock>>
+ Sized
Expand Down
4 changes: 2 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -475,7 +475,7 @@ pub fn apply<Op, S1, S2>(op: Op, s1: S1, s2: S2) -> BitSetOp<Op, S1, S2>
where
Op: BinaryOp,
S1: BitSetInterface,
S2: BitSetInterface<Conf= <S1 as BitSetBase>::Conf>,
S2: BitSetInterface<Conf = <S1 as BitSetBase>::Conf>,
{
BitSetOp::new(op, s1, s2)
}
Expand All @@ -500,7 +500,7 @@ where
Conf: Config,
Op: BinaryOp,
S: Iterator + Clone,
S::Item: BitSetInterface<Conf=Conf>,
S::Item: BitSetInterface<Conf = Conf>,
{
reduce_w_cache(op, sets, Default::default())
}
Expand Down
6 changes: 6 additions & 0 deletions src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,12 @@ fn fuzzy_test(){
let h2 = hi_set.contains(index);
assert_eq!(h1, h2);
}

// eq
{
let other: HiSparseBitset = hi_set.iter().collect();
assert!(hi_set == other);
}
}
}
}
Expand Down