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