Skip to main content

vortex_buffer/bit/
buf.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4use std::fmt::Display;
5use std::fmt::Formatter;
6use std::fmt::Result as FmtResult;
7use std::ops::BitAnd;
8use std::ops::BitOr;
9use std::ops::BitXor;
10use std::ops::Not;
11use std::ops::RangeBounds;
12
13use crate::Alignment;
14use crate::BitBufferMeta;
15use crate::BitBufferMut;
16use crate::Buffer;
17use crate::BufferMut;
18use crate::ByteBuffer;
19use crate::bit::BitChunks;
20use crate::bit::BitIndexIterator;
21use crate::bit::BitIterator;
22use crate::bit::BitSliceIterator;
23use crate::bit::UnalignedBitChunk;
24use crate::bit::collect_bool_word;
25use crate::bit::count_ones::count_ones;
26use crate::bit::get_bit_unchecked;
27use crate::bit::ops::bitwise_binary_op;
28use crate::bit::ops::bitwise_binary_op_lhs_owned;
29use crate::bit::ops::bitwise_unary_op;
30use crate::bit::ops::bitwise_unary_op_copy;
31use crate::bit::select::bit_select;
32use crate::buffer;
33
34/// An immutable bitset stored as a packed byte buffer.
35#[derive(Debug, Clone, Eq)]
36#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
37pub struct BitBuffer {
38    buffer: ByteBuffer,
39    /// Represents the offset of the bit buffer into the first byte.
40    ///
41    /// This is always less than 8 (for when the bit buffer is not aligned to a byte).
42    offset: usize,
43    len: usize,
44}
45
46const LIMIT_LEN: usize = 16;
47impl Display for BitBuffer {
48    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
49        let limit = f.precision().unwrap_or(LIMIT_LEN);
50        let buf: Vec<bool> = self.into_iter().take(limit).collect();
51        f.debug_struct("BitBuffer")
52            .field("len", &self.len)
53            .field("buffer", &buf)
54            .finish()
55    }
56}
57
58impl PartialEq for BitBuffer {
59    fn eq(&self, other: &Self) -> bool {
60        if self.len != other.len {
61            return false;
62        }
63
64        if self.len == 0 {
65            return true;
66        }
67
68        // Fast path: both byte-aligned and same length — direct byte comparison.
69        if self.offset == 0 && other.offset == 0 {
70            let full_bytes = self.len / 8;
71            let self_bytes = &self.buffer.as_slice()[..full_bytes];
72            let other_bytes = &other.buffer.as_slice()[..full_bytes];
73            if self_bytes != other_bytes {
74                return false;
75            }
76            // Compare remaining bits in the last partial byte.
77            let rem = self.len % 8;
78            if rem != 0 {
79                let mask = (1u8 << rem) - 1;
80                let a = self.buffer.as_slice()[full_bytes] & mask;
81                let b = other.buffer.as_slice()[full_bytes] & mask;
82                return a == b;
83            }
84            return true;
85        }
86
87        self.chunks()
88            .iter_padded()
89            .zip(other.chunks().iter_padded())
90            .all(|(a, b)| a == b)
91    }
92}
93
94impl BitBuffer {
95    /// Create a new `BoolBuffer` backed by a [`ByteBuffer`] with `len` bits in view.
96    ///
97    /// Panics if the buffer is not large enough to hold `len` bits.
98    pub fn new(buffer: ByteBuffer, len: usize) -> Self {
99        assert!(
100            buffer.len() * 8 >= len,
101            "provided ByteBuffer not large enough to back BoolBuffer with len {len}"
102        );
103
104        // BitBuffers make no assumptions on byte alignment, so we strip any alignment.
105        let buffer = buffer.aligned(Alignment::none());
106
107        Self {
108            buffer,
109            len,
110            offset: 0,
111        }
112    }
113
114    /// Create a new `BoolBuffer` backed by a [`ByteBuffer`] with `len` bits in view, starting at
115    /// the given `offset` (in bits).
116    ///
117    /// Panics if the buffer is not large enough to hold `len` bits after the offset.
118    pub fn new_with_offset(buffer: ByteBuffer, len: usize, offset: usize) -> Self {
119        assert!(
120            len.saturating_add(offset) <= buffer.len().saturating_mul(8),
121            "provided ByteBuffer (len={}) not large enough to back BoolBuffer with offset {offset} len {len}",
122            buffer.len()
123        );
124
125        // BitBuffers make no assumptions on byte alignment, so we strip any alignment.
126        let buffer = buffer.aligned(Alignment::none());
127
128        // Slice the buffer to ensure the offset is within the first byte
129        let byte_offset = offset / 8;
130        let offset = offset % 8;
131        let buffer = if byte_offset != 0 {
132            buffer.slice(byte_offset..)
133        } else {
134            buffer
135        };
136
137        Self {
138            buffer,
139            offset,
140            len,
141        }
142    }
143
144    /// Create a new `BoolBuffer` of length `len` where all bits are set (true).
145    pub fn new_set(len: usize) -> Self {
146        let words = len.div_ceil(8);
147        let buffer = buffer![0xFF; words];
148
149        Self {
150            buffer,
151            len,
152            offset: 0,
153        }
154    }
155
156    /// Create a new `BoolBuffer` of length `len` where all bits are unset (false).
157    pub fn new_unset(len: usize) -> Self {
158        let words = len.div_ceil(8);
159        let buffer = Buffer::zeroed(words);
160
161        Self {
162            buffer,
163            len,
164            offset: 0,
165        }
166    }
167
168    /// Create a bit buffer of `len` with `indices` set as true.
169    pub fn from_indices(len: usize, indices: impl IntoIterator<Item = usize>) -> BitBuffer {
170        BitBufferMut::from_indices(len, indices).freeze()
171    }
172
173    /// Create a new empty `BitBuffer`.
174    pub fn empty() -> Self {
175        Self::new_set(0)
176    }
177
178    /// Create a new `BitBuffer` of length `len` where all bits are set to `value`.
179    pub fn full(value: bool, len: usize) -> Self {
180        if value {
181            Self::new_set(len)
182        } else {
183            Self::new_unset(len)
184        }
185    }
186
187    /// Invokes `f` with indexes `0..len` collecting the boolean results into a new [`BitBuffer`].
188    ///
189    /// `f` is invoked exactly once per index, in ascending order, and the results are packed
190    /// with the baseline SIMD byte→bit instruction of the target.
191    ///
192    /// # Performance
193    ///
194    /// The packing is a few instructions per 64 bits, so evaluating `f` is usually the
195    /// bottleneck. In particular, a bounds-checked slice access in `f` (`|i| values[i] > x`)
196    /// blocks vectorization of the gather and can cost ~10x the packing itself. Since `f` only
197    /// ever sees indices `0..len`, callers reading from a slice with `len <= values.len()` may
198    /// soundly use `|i| unsafe { *values.get_unchecked(i) }`.
199    ///
200    /// Prefer this entry point for every predicate. Only switch to
201    /// [`Self::collect_bool_multiversioned`] after carefully checking that your specific `f`
202    /// meets its contract (a trivially cheap, bounds-check-free gather or comparison) —
203    /// ideally with a benchmark.
204    #[inline]
205    pub fn collect_bool<F: FnMut(usize) -> bool>(len: usize, f: F) -> Self {
206        BitBufferMut::collect_bool(len, f).freeze()
207    }
208
209    /// Like [`Self::collect_bool`], but compiles the packing loop — with `f` inside it — once
210    /// per CPU feature level (AVX-512BW/AVX2/baseline) and selects a clone by runtime feature
211    /// detection.
212    ///
213    /// Calling this asserts that `f` is small and simple enough (e.g. a bounds-check-free slice
214    /// gather or comparison) that duplicating it per feature level and paying a
215    /// `#[target_feature]` call boundary beats inlining it once into your function. For any
216    /// non-trivial `f` that assertion is false — the boundary deoptimizes the predicate — so
217    /// unless you have carefully checked (ideally benchmarked) that your specific `f`
218    /// qualifies, use [`Self::collect_bool`]. See
219    /// [`collect_bool_words_multiversioned`](crate::bit::collect_bool_words_multiversioned).
220    #[inline]
221    pub fn collect_bool_multiversioned<F: FnMut(usize) -> bool>(len: usize, f: F) -> Self {
222        BitBufferMut::collect_bool_multiversioned(len, f).freeze()
223    }
224
225    /// Maps over each bit in this buffer, calling `f(index, bit_value)` and collecting results.
226    ///
227    /// This is more efficient than `collect_bool` when you need to read the current bit value,
228    /// as it unpacks each u64 chunk only once rather than doing random access for each bit.
229    pub fn map_cmp<F>(&self, mut f: F) -> Self
230    where
231        F: FnMut(usize, bool) -> bool,
232    {
233        let len = self.len;
234        let mut buffer: BufferMut<u64> = BufferMut::with_capacity(len.div_ceil(64));
235
236        let chunks_count = len / 64;
237        let remainder = len % 64;
238        let chunks = self.chunks();
239
240        for (chunk_idx, src_chunk) in chunks.iter().enumerate() {
241            let packed = collect_bool_word(64, |bit_idx| {
242                let i = bit_idx + chunk_idx * 64;
243                let bit_value = (src_chunk >> bit_idx) & 1 == 1;
244                f(i, bit_value)
245            });
246
247            // SAFETY: Already allocated sufficient capacity
248            unsafe { buffer.push_unchecked(packed) }
249        }
250
251        if remainder != 0 {
252            let src_chunk = chunks.remainder_bits();
253            let packed = collect_bool_word(remainder, |bit_idx| {
254                let i = bit_idx + chunks_count * 64;
255                let bit_value = (src_chunk >> bit_idx) & 1 == 1;
256                f(i, bit_value)
257            });
258
259            // SAFETY: Already allocated sufficient capacity
260            unsafe { buffer.push_unchecked(packed) }
261        }
262
263        let mut bytes = buffer.into_byte_buffer();
264        bytes.truncate(len.div_ceil(8));
265
266        Self {
267            buffer: bytes.freeze(),
268            offset: 0,
269            len,
270        }
271    }
272
273    /// Clear all bits in the buffer, preserving existing capacity.
274    pub fn clear(&mut self) {
275        self.buffer.clear();
276        self.len = 0;
277        self.offset = 0;
278    }
279
280    /// Get the logical length of this `BoolBuffer`.
281    ///
282    /// This may differ from the physical length of the backing buffer, for example if it was
283    /// created using the `new_with_offset` constructor, or if it was sliced.
284    #[inline]
285    pub fn len(&self) -> usize {
286        self.len
287    }
288
289    /// Returns `true` if the `BoolBuffer` is empty.
290    #[inline]
291    pub fn is_empty(&self) -> bool {
292        self.len() == 0
293    }
294
295    /// Offset of the start of the buffer in bits.
296    #[inline(always)]
297    pub fn offset(&self) -> usize {
298        self.offset
299    }
300
301    /// Get a reference to the underlying buffer.
302    #[inline(always)]
303    pub fn inner(&self) -> &ByteBuffer {
304        &self.buffer
305    }
306
307    /// Return the backing bytes for this bit buffer when its logical offset is byte-aligned.
308    ///
309    /// The returned slice contains exactly `self.len().div_ceil(8)` bytes. Bits past the logical
310    /// length in the final byte are outside the buffer's logical range and should be ignored by
311    /// callers.
312    #[inline]
313    pub fn byte_aligned_bytes(&self) -> Option<&[u8]> {
314        if !self.offset.is_multiple_of(8) {
315            return None;
316        }
317
318        let n_bytes = self.len.div_ceil(8);
319        let start = self.offset / 8;
320        let end = start + n_bytes;
321        Some(&self.buffer.as_slice()[start..end])
322    }
323
324    /// Retrieve the value at the given index.
325    ///
326    /// Panics if the index is out of bounds.
327    ///
328    /// Please note for repeatedly calling this function, please prefer [`crate::get_bit`].
329    #[inline]
330    pub fn value(&self, index: usize) -> bool {
331        assert!(index < self.len);
332        unsafe { self.value_unchecked(index) }
333    }
334
335    /// Retrieve the value at the given index without bounds checking
336    ///
337    /// # SAFETY
338    /// Caller must ensure that index is within the range of the buffer
339    #[inline]
340    pub unsafe fn value_unchecked(&self, index: usize) -> bool {
341        unsafe { get_bit_unchecked(self.buffer.as_ptr(), index + self.offset) }
342    }
343
344    /// Create a new zero-copy slice of this BoolBuffer that begins at the `start` index and extends
345    /// for `len` bits.
346    ///
347    /// Panics if the slice would extend beyond the end of the buffer.
348    pub fn slice(&self, range: impl RangeBounds<usize>) -> Self {
349        let (byte_offset, meta) = BitBufferMeta::new(self.offset, self.len).slice(range);
350
351        // Trim whole bytes off the front directly rather than going through `new_with_offset`,
352        // which would slice (and re-clone) the clone we'd have to pass it.
353        let buffer = if byte_offset != 0 {
354            self.buffer.slice_unaligned(byte_offset..)
355        } else {
356            self.buffer.clone().aligned(Alignment::none())
357        };
358
359        Self {
360            buffer,
361            offset: meta.offset(),
362            len: meta.len(),
363        }
364    }
365
366    /// Slice any full bytes from the buffer, leaving the offset < 8.
367    pub fn shrink_offset(self) -> Self {
368        let word_start = self.offset / 8;
369        let word_end = (self.offset + self.len).div_ceil(8);
370
371        let buffer = self.buffer.slice(word_start..word_end);
372
373        let bit_offset = self.offset % 8;
374        let len = self.len;
375        BitBuffer::new_with_offset(buffer, len, bit_offset)
376    }
377
378    /// Access chunks of the buffer aligned to 8 byte boundary as [prefix, \<full chunks\>, suffix]
379    pub fn unaligned_chunks(&self) -> UnalignedBitChunk<'_> {
380        UnalignedBitChunk::new(self.buffer.as_slice(), self.offset, self.len)
381    }
382
383    /// Access chunks of the underlying buffer as 8 byte chunks with a final trailer
384    ///
385    /// If you're performing operations on a single buffer, prefer [BitBuffer::unaligned_chunks]
386    pub fn chunks(&self) -> BitChunks<'_> {
387        BitChunks::new(self.buffer.as_slice(), self.offset, self.len)
388    }
389
390    /// Get the number of set bits in the buffer.
391    #[inline]
392    pub fn true_count(&self) -> usize {
393        count_ones(self.buffer.as_slice(), self.offset, self.len)
394    }
395
396    /// Get the number of set bits in the bit range `[start, end)`.
397    ///
398    /// Unlike `self.slice(start..end).true_count()`, this counts directly over the
399    /// existing backing buffer without allocating or cloning a new [`BitBuffer`],
400    /// making it cheap to call repeatedly over many small ranges.
401    ///
402    /// Panics if `start > end` or `end > len`.
403    #[inline]
404    pub fn count_range(&self, start: usize, end: usize) -> usize {
405        assert!(start <= end, "start {start} exceeds end {end}");
406        assert!(end <= self.len, "end {end} exceeds len {}", self.len);
407        count_ones(self.buffer.as_slice(), self.offset + start, end - start)
408    }
409
410    /// Returns the position of the `nth` set bit (0-indexed).
411    ///
412    /// This is the "select" operation on a bitmap: given a rank `nth`, find
413    /// which logical bit position holds that rank.
414    ///
415    /// Returns `None` if `nth` is greater than or equal to the number of set bits.
416    pub fn select(&self, nth: usize) -> Option<usize> {
417        bit_select(self.buffer.as_slice(), self.offset, self.len, nth)
418    }
419
420    /// Get the number of unset bits in the buffer.
421    #[inline]
422    pub fn false_count(&self) -> usize {
423        self.len - self.true_count()
424    }
425
426    /// Iterator over bits in the buffer
427    pub fn iter(&self) -> BitIterator<'_> {
428        BitIterator::new(self.buffer.as_slice(), self.offset, self.len)
429    }
430
431    /// Iterator over set indices of the underlying buffer
432    pub fn set_indices(&self) -> BitIndexIterator<'_> {
433        BitIndexIterator::new(self.buffer.as_slice(), self.offset, self.len)
434    }
435
436    /// Iterator over set slices of the underlying buffer
437    pub fn set_slices(&self) -> BitSliceIterator<'_> {
438        BitSliceIterator::new(self.buffer.as_slice(), self.offset, self.len)
439    }
440
441    /// Invoke `f(index)` for every set bit, in ascending order, processing a `u64`
442    /// word at a time.
443    ///
444    /// This is the fast way to "do something for each set bit": it skips all-zero
445    /// words, fast-paths all-one words, and walks the remaining bits with
446    /// `trailing_zeros`. Prefer it over `for i in 0..len { if buf.value(i) { f(i) } }`
447    /// (which pays a branch per element) and over collecting [`Self::set_indices`]
448    /// (whose per-`next` iterator state does not inline as well).
449    #[inline]
450    pub fn for_each_set_index<F: FnMut(usize)>(&self, mut f: F) {
451        let mut base = 0usize;
452        for word in self.chunks().iter_padded() {
453            if word == u64::MAX {
454                for k in 0..64 {
455                    f(base + k);
456                }
457            } else {
458                let mut w = word;
459                while w != 0 {
460                    f(base + w.trailing_zeros() as usize);
461                    w &= w - 1;
462                }
463            }
464            base += 64;
465        }
466    }
467
468    /// Created a new BitBuffer with offset reset to 0
469    pub fn sliced(&self) -> Self {
470        if self.offset.is_multiple_of(8) {
471            return Self::new(
472                self.buffer
473                    .slice(self.offset / 8..(self.offset + self.len).div_ceil(8)),
474                self.len,
475            );
476        }
477
478        // Allocate directly rather than clone + identity op which would fail try_into_mut.
479        bitwise_unary_op_copy(self, |a| a)
480    }
481}
482
483// Conversions
484
485impl BitBuffer {
486    /// Returns the offset, len and underlying buffer.
487    pub fn into_inner(self) -> (usize, usize, ByteBuffer) {
488        (self.offset, self.len, self.buffer)
489    }
490
491    /// Attempt to convert this `BitBuffer` into a mutable version.
492    pub fn try_into_mut(self) -> Result<BitBufferMut, Self> {
493        match self.buffer.try_into_mut() {
494            Ok(buffer) => Ok(BitBufferMut::from_buffer(buffer, self.offset, self.len)),
495            Err(buffer) => Err(BitBuffer::new_with_offset(buffer, self.len, self.offset)),
496        }
497    }
498}
499
500impl From<&[bool]> for BitBuffer {
501    fn from(value: &[bool]) -> Self {
502        BitBufferMut::from(value).freeze()
503    }
504}
505
506impl From<Vec<bool>> for BitBuffer {
507    fn from(value: Vec<bool>) -> Self {
508        BitBufferMut::from(value).freeze()
509    }
510}
511
512impl FromIterator<bool> for BitBuffer {
513    fn from_iter<T: IntoIterator<Item = bool>>(iter: T) -> Self {
514        BitBufferMut::from_iter(iter).freeze()
515    }
516}
517
518impl BitOr for BitBuffer {
519    type Output = Self;
520
521    #[inline]
522    fn bitor(self, rhs: Self) -> Self::Output {
523        bitwise_binary_op_lhs_owned(self, &rhs, |a, b| a | b)
524    }
525}
526
527impl BitOr for &BitBuffer {
528    type Output = BitBuffer;
529
530    #[inline]
531    fn bitor(self, rhs: Self) -> Self::Output {
532        bitwise_binary_op(self, rhs, |a, b| a | b)
533    }
534}
535
536impl BitOr<&BitBuffer> for BitBuffer {
537    type Output = BitBuffer;
538
539    #[inline]
540    fn bitor(self, rhs: &BitBuffer) -> Self::Output {
541        bitwise_binary_op_lhs_owned(self, rhs, |a, b| a | b)
542    }
543}
544
545impl BitAnd for &BitBuffer {
546    type Output = BitBuffer;
547
548    #[inline]
549    fn bitand(self, rhs: Self) -> Self::Output {
550        bitwise_binary_op(self, rhs, |a, b| a & b)
551    }
552}
553
554impl BitAnd<BitBuffer> for &BitBuffer {
555    type Output = BitBuffer;
556
557    #[inline]
558    fn bitand(self, rhs: BitBuffer) -> Self::Output {
559        self.bitand(&rhs)
560    }
561}
562
563impl BitAnd<&BitBuffer> for BitBuffer {
564    type Output = BitBuffer;
565
566    #[inline]
567    fn bitand(self, rhs: &BitBuffer) -> Self::Output {
568        bitwise_binary_op_lhs_owned(self, rhs, |a, b| a & b)
569    }
570}
571
572impl BitAnd<BitBuffer> for BitBuffer {
573    type Output = BitBuffer;
574
575    #[inline]
576    fn bitand(self, rhs: BitBuffer) -> Self::Output {
577        bitwise_binary_op_lhs_owned(self, &rhs, |a, b| a & b)
578    }
579}
580
581impl Not for &BitBuffer {
582    type Output = BitBuffer;
583
584    #[inline]
585    fn not(self) -> Self::Output {
586        // Allocate directly rather than clone+try_into_mut, which always fails
587        // since the clone shares the Arc with the original reference.
588        bitwise_unary_op_copy(self, |a| !a)
589    }
590}
591
592impl Not for BitBuffer {
593    type Output = BitBuffer;
594
595    #[inline]
596    fn not(self) -> Self::Output {
597        bitwise_unary_op(self, |a| !a)
598    }
599}
600
601impl BitXor for &BitBuffer {
602    type Output = BitBuffer;
603
604    #[inline]
605    fn bitxor(self, rhs: Self) -> Self::Output {
606        bitwise_binary_op(self, rhs, |a, b| a ^ b)
607    }
608}
609
610impl BitXor<&BitBuffer> for BitBuffer {
611    type Output = BitBuffer;
612
613    #[inline]
614    fn bitxor(self, rhs: &BitBuffer) -> Self::Output {
615        bitwise_binary_op_lhs_owned(self, rhs, |a, b| a ^ b)
616    }
617}
618
619impl BitBuffer {
620    /// Create a new BitBuffer by performing a bitwise AND NOT operation between two BitBuffers.
621    ///
622    /// This operation is sufficiently common that we provide a dedicated method for it avoid
623    /// making two passes over the data.
624    pub fn bitand_not(&self, rhs: &BitBuffer) -> BitBuffer {
625        bitwise_binary_op(self, rhs, |a, b| a & !b)
626    }
627
628    /// Owned variant of [`bitand_not`](Self::bitand_not) that can mutate in-place when possible.
629    pub fn into_bitand_not(self, rhs: &BitBuffer) -> BitBuffer {
630        bitwise_binary_op_lhs_owned(self, rhs, |a, b| a & !b)
631    }
632
633    /// Iterate through bits in a buffer.
634    ///
635    /// # Arguments
636    ///
637    /// * `f` - Callback function taking (bit_index, is_set)
638    ///
639    /// # Panics
640    ///
641    /// Panics if the range is outside valid bounds of the buffer.
642    #[inline]
643    pub fn iter_bits<F>(&self, mut f: F)
644    where
645        F: FnMut(usize, bool),
646    {
647        let total_bits = self.len;
648        if total_bits == 0 {
649            return;
650        }
651
652        // Process in 64-bit chunks for better ILP and fewer loop iterations.
653        let chunks = self.chunks();
654        let chunks_count = total_bits / 64;
655        let remainder = total_bits % 64;
656
657        for (chunk_idx, chunk) in chunks.iter().enumerate() {
658            let base = chunk_idx * 64;
659            for bit_idx in 0..64 {
660                f(base + bit_idx, (chunk >> bit_idx) & 1 == 1);
661            }
662        }
663
664        if remainder != 0 {
665            let rem_chunk = chunks.remainder_bits();
666            let base = chunks_count * 64;
667            for bit_idx in 0..remainder {
668                f(base + bit_idx, (rem_chunk >> bit_idx) & 1 == 1);
669            }
670        }
671    }
672}
673
674impl<'a> IntoIterator for &'a BitBuffer {
675    type Item = bool;
676    type IntoIter = BitIterator<'a>;
677
678    fn into_iter(self) -> Self::IntoIter {
679        self.iter()
680    }
681}
682
683#[cfg(test)]
684mod tests {
685    use rstest::rstest;
686
687    use crate::ByteBuffer;
688    use crate::bit::BitBuffer;
689    use crate::buffer;
690
691    #[test]
692    fn test_bool() {
693        // Create a new Buffer<u64> of length 1024 where the 8th bit is set.
694        let buffer: ByteBuffer = buffer![1 << 7; 1024];
695        let bools = BitBuffer::new(buffer, 1024 * 8);
696
697        // sanity checks
698        assert_eq!(bools.len(), 1024 * 8);
699        assert!(!bools.is_empty());
700        assert_eq!(bools.true_count(), 1024);
701        assert_eq!(bools.false_count(), 1024 * 7);
702
703        // Check all the values
704        for word in 0..1024 {
705            for bit in 0..8 {
706                if bit == 7 {
707                    assert!(bools.value(word * 8 + bit));
708                } else {
709                    assert!(!bools.value(word * 8 + bit));
710                }
711            }
712        }
713
714        // Slice the buffer to create a new subset view.
715        let sliced = bools.slice(64..72);
716
717        // sanity checks
718        assert_eq!(sliced.len(), 8);
719        assert!(!sliced.is_empty());
720        assert_eq!(sliced.true_count(), 1);
721        assert_eq!(sliced.false_count(), 7);
722
723        // Check all of the values like before
724        for bit in 0..8 {
725            if bit == 7 {
726                assert!(sliced.value(bit));
727            } else {
728                assert!(!sliced.value(bit));
729            }
730        }
731    }
732
733    #[test]
734    fn test_padded_equaltiy() {
735        let buf1 = BitBuffer::new_set(64); // All bits set.
736        let buf2 = BitBuffer::collect_bool(64, |x| x < 32); // First half set, other half unset.
737
738        for i in 0..32 {
739            assert_eq!(buf1.value(i), buf2.value(i), "Bit {} should be the same", i);
740        }
741
742        for i in 32..64 {
743            assert_ne!(buf1.value(i), buf2.value(i), "Bit {} should differ", i);
744        }
745
746        assert_eq!(
747            buf1.slice(0..32),
748            buf2.slice(0..32),
749            "Buffer slices with same bits should be equal (`PartialEq` needs `iter_padded()`)"
750        );
751        assert_ne!(
752            buf1.slice(32..64),
753            buf2.slice(32..64),
754            "Buffer slices with different bits should not be equal (`PartialEq` needs `iter_padded()`)"
755        );
756    }
757
758    #[test]
759    fn test_slice_offset_calculation() {
760        let buf = BitBuffer::collect_bool(16, |_| true);
761        let sliced = buf.slice(10..16);
762        assert_eq!(sliced.len(), 6);
763        // Ensure the offset is modulo 8
764        assert_eq!(sliced.offset(), 2);
765    }
766
767    #[test]
768    fn test_byte_aligned_bytes() {
769        let bytes: ByteBuffer = buffer![0b1010_0101u8, 0b0000_0011];
770        let buf = BitBuffer::new(bytes.clone(), 10);
771        assert_eq!(buf.byte_aligned_bytes(), Some(bytes.as_slice()));
772
773        let byte_sliced = buf.slice(8..10);
774        assert_eq!(byte_sliced.byte_aligned_bytes(), Some(&[0b0000_0011][..]));
775
776        let bit_sliced = buf.slice(1..9);
777        assert!(bit_sliced.byte_aligned_bytes().is_none());
778    }
779
780    #[test]
781    fn test_from_indices_dense_crosses_words() {
782        let len = 130;
783        let indices = (0..len).filter(|idx| idx % 3 != 1);
784        let buf = BitBuffer::from_indices(len, indices);
785
786        assert_eq!(buf.len(), len);
787        for idx in 0..len {
788            assert_eq!(buf.value(idx), idx % 3 != 1, "mismatch at {idx}");
789        }
790    }
791
792    #[test]
793    #[should_panic(expected = "index 5 exceeds len 5")]
794    fn test_from_indices_out_of_bounds() {
795        BitBuffer::from_indices(5, [0, 5]);
796    }
797
798    #[rstest]
799    #[case(5)]
800    #[case(8)]
801    #[case(10)]
802    #[case(13)]
803    #[case(16)]
804    #[case(23)]
805    #[case(100)]
806    fn test_iter_bits(#[case] len: usize) {
807        let buf = BitBuffer::collect_bool(len, |i| i % 2 == 0);
808
809        let mut collected = Vec::new();
810        buf.iter_bits(|idx, is_set| {
811            collected.push((idx, is_set));
812        });
813
814        assert_eq!(collected.len(), len);
815
816        for (idx, is_set) in collected {
817            assert_eq!(is_set, idx % 2 == 0);
818        }
819    }
820
821    #[rstest]
822    #[case(3, 5)]
823    #[case(3, 8)]
824    #[case(5, 10)]
825    #[case(2, 16)]
826    #[case(8, 16)]
827    #[case(9, 16)]
828    #[case(17, 16)]
829    fn test_iter_bits_with_offset(#[case] offset: usize, #[case] len: usize) {
830        let total_bits = offset + len;
831        let buf = BitBuffer::collect_bool(total_bits, |i| i % 2 == 0);
832        let buf_with_offset = BitBuffer::new_with_offset(buf.inner().clone(), len, offset);
833
834        let mut collected = Vec::new();
835        buf_with_offset.iter_bits(|idx, is_set| {
836            collected.push((idx, is_set));
837        });
838
839        assert_eq!(collected.len(), len);
840
841        for (idx, is_set) in collected {
842            // The bits should match the original buffer at positions offset + idx
843            assert_eq!(is_set, (offset + idx).is_multiple_of(2));
844        }
845    }
846
847    #[rstest]
848    #[case(8, 10)]
849    #[case(9, 7)]
850    #[case(16, 8)]
851    #[case(17, 10)]
852    fn test_iter_bits_catches_wrong_byte_offset(#[case] offset: usize, #[case] len: usize) {
853        let total_bits = offset + len;
854        // Alternating pattern to catch byte offset errors: Bits are set for even indexed bytes.
855        let buf = BitBuffer::collect_bool(total_bits, |i| (i / 8) % 2 == 0);
856
857        let buf_with_offset = BitBuffer::new_with_offset(buf.inner().clone(), len, offset);
858
859        let mut collected = Vec::new();
860        buf_with_offset.iter_bits(|idx, is_set| {
861            collected.push((idx, is_set));
862        });
863
864        assert_eq!(collected.len(), len);
865
866        for (idx, is_set) in collected {
867            let bit_position = offset + idx;
868            let byte_index = bit_position / 8;
869            let expected_is_set = byte_index.is_multiple_of(2);
870
871            assert_eq!(
872                is_set, expected_is_set,
873                "Bit mismatch at index {}: expected {} got {}",
874                bit_position, expected_is_set, is_set
875            );
876        }
877    }
878
879    #[rstest]
880    #[case(5)]
881    #[case(8)]
882    #[case(10)]
883    #[case(64)]
884    #[case(65)]
885    #[case(100)]
886    #[case(128)]
887    fn test_map_cmp_identity(#[case] len: usize) {
888        // map_cmp with identity function should return the same buffer
889        let buf = BitBuffer::collect_bool(len, |i| i % 3 == 0);
890        let mapped = buf.map_cmp(|_idx, bit| bit);
891
892        assert_eq!(buf.len(), mapped.len());
893        for i in 0..len {
894            assert_eq!(buf.value(i), mapped.value(i), "Mismatch at index {}", i);
895        }
896    }
897
898    #[rstest]
899    #[case(5)]
900    #[case(8)]
901    #[case(64)]
902    #[case(65)]
903    #[case(100)]
904    fn test_map_cmp_negate(#[case] len: usize) {
905        // map_cmp negating all bits
906        let buf = BitBuffer::collect_bool(len, |i| i % 2 == 0);
907        let mapped = buf.map_cmp(|_idx, bit| !bit);
908
909        assert_eq!(buf.len(), mapped.len());
910        for i in 0..len {
911            assert_eq!(!buf.value(i), mapped.value(i), "Mismatch at index {}", i);
912        }
913    }
914
915    #[rstest]
916    #[case(0, 0)]
917    #[case(0, 64)]
918    #[case(5, 70)]
919    #[case(64, 130)]
920    #[case(0, 200)]
921    fn test_count_range(#[case] start: usize, #[case] end: usize) {
922        let len = 200;
923        let buf = BitBuffer::collect_bool(len, |i| i % 3 == 0);
924        let expected = (start..end).filter(|i| i % 3 == 0).count();
925        assert_eq!(buf.count_range(start, end), expected);
926        // Must agree with slicing then counting.
927        assert_eq!(
928            buf.count_range(start, end),
929            buf.slice(start..end).true_count()
930        );
931    }
932
933    #[rstest]
934    #[case(3)]
935    #[case(7)]
936    fn test_count_range_with_offset(#[case] offset: usize) {
937        let len = 150;
938        let buf = BitBuffer::collect_bool(offset + len, |i| i % 2 == 0);
939        let view = BitBuffer::new_with_offset(buf.inner().clone(), len, offset);
940        for (start, end) in [(0, len), (10, 100), (1, 2), (63, 129)] {
941            let expected = (offset + start..offset + end)
942                .filter(|i| i % 2 == 0)
943                .count();
944            assert_eq!(view.count_range(start, end), expected, "[{start}, {end})");
945        }
946    }
947
948    #[rstest]
949    #[case(0)]
950    #[case(1)]
951    #[case(63)]
952    #[case(64)]
953    #[case(65)]
954    #[case(200)]
955    #[case(1000)]
956    fn test_for_each_set_index_matches_set_indices(#[case] len: usize) {
957        let buf = BitBuffer::collect_bool(len, |i| i % 5 == 0 || i % 7 == 0);
958        let expected: Vec<usize> = buf.set_indices().collect();
959        let mut got = Vec::new();
960        buf.for_each_set_index(|i| got.push(i));
961        assert_eq!(got, expected);
962    }
963
964    #[rstest]
965    #[case(3, 200)]
966    #[case(7, 130)]
967    fn test_for_each_set_index_with_offset(#[case] offset: usize, #[case] len: usize) {
968        let base = BitBuffer::collect_bool(offset + len, |i| i % 3 == 0);
969        let view = BitBuffer::new_with_offset(base.inner().clone(), len, offset);
970        let expected: Vec<usize> = view.set_indices().collect();
971        let mut got = Vec::new();
972        view.for_each_set_index(|i| got.push(i));
973        assert_eq!(got, expected);
974    }
975
976    #[test]
977    fn test_for_each_set_index_all_set() {
978        let buf = BitBuffer::new_set(130);
979        let mut got = Vec::new();
980        buf.for_each_set_index(|i| got.push(i));
981        assert_eq!(got, (0..130).collect::<Vec<_>>());
982    }
983
984    #[test]
985    fn test_map_cmp_conditional() {
986        // map_cmp with conditional logic based on index and bit value
987        let len = 100;
988        let buf = BitBuffer::collect_bool(len, |i| i % 2 == 0);
989
990        // Only keep bits that are set AND at even index divisible by 4
991        let mapped = buf.map_cmp(|idx, bit| bit && idx % 4 == 0);
992
993        for i in 0..len {
994            let expected = (i % 2 == 0) && (i % 4 == 0);
995            assert_eq!(mapped.value(i), expected, "Mismatch at index {}", i);
996        }
997    }
998}