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    /// Collects `len` Boolean values from `f` into a packed [`BitBuffer`].
195    ///
196    /// Calls `f` exactly once for each index in `0..len`, in order.
197    ///
198    /// # Code generation
199    ///
200    /// `collect_bool_words` calls `collect_bool_words_inline`, which selects a `pack_bool_word_*`
201    /// kernel at compile time and passes it to `collect_bool_words_with`. That shared word loop
202    /// materializes each full 64-value chunk as a byte-per-value `[bool; 64]`, then passes it to the
203    /// selected kernel. For simple predicates, LLVM vectorizes the loop and removes the physical
204    /// stack array. On AVX-512, it still combines the comparison masks, expands the result into 64
205    /// `0` or `1` bytes with `vpbroadcastq` and `vmovdqu8`, then recreates the mask with `vptestmb`.
206    /// [LLVM issue #219235](https://github.com/llvm/llvm-project/issues/219235) tracks replacing
207    /// that round trip with a direct `kmovq` store. The conversion is per chunk. This method does
208    /// not create a full-column byte buffer.
209    ///
210    /// # Performance
211    ///
212    /// `collect_bool_words_inline` and `collect_bool_words_with` can inline into the caller, so LLVM
213    /// sees `f`, the fill loop, and the packing kernel together. A retained bounds check inside `f`
214    /// can prevent vectorization. A caller that proves `len <= values.len()` can use
215    /// `unsafe { *values.get_unchecked(i) }` because this method only passes indices in `0..len`.
216    ///
217    /// Use this method for general predicates. Use [`Self::collect_bool_multiversioned`] only for
218    /// the specialized predicates described there.
219    #[inline]
220    pub fn collect_bool<F: FnMut(usize) -> bool>(len: usize, f: F) -> Self {
221        BitBufferMut::collect_bool(len, f).freeze()
222    }
223
224    /// Collects Boolean values with a fill-and-pack loop selected for the current CPU.
225    ///
226    /// This has the same callback contract as [`Self::collect_bool`]. On x86-64,
227    /// `collect_bool_words_multiversioned` selects `collect_bool_words_avx512`,
228    /// `collect_bool_words_avx2`, or `collect_bool_words_inline` at runtime. Each wider version is a
229    /// `#[target_feature]` function, so Rust cannot inline it into a caller compiled without those
230    /// features.
231    ///
232    /// Use this method only for a small, bounds-check-free predicate whose wider loop has been
233    /// benchmarked. Use [`Self::collect_bool`] for general predicates.
234    #[inline]
235    pub fn collect_bool_multiversioned<F: FnMut(usize) -> bool>(len: usize, f: F) -> Self {
236        BitBufferMut::collect_bool_multiversioned(len, f).freeze()
237    }
238
239    /// Maps over each bit in this buffer, calling `f(index, bit_value)` and collecting results.
240    ///
241    /// This is more efficient than `collect_bool` when you need to read the current bit value,
242    /// as it unpacks each u64 chunk only once rather than doing random access for each bit.
243    pub fn map_cmp<F>(&self, mut f: F) -> Self
244    where
245        F: FnMut(usize, bool) -> bool,
246    {
247        let len = self.len;
248        let mut buffer: BufferMut<u64> = BufferMut::with_capacity(len.div_ceil(64));
249
250        let chunks_count = len / 64;
251        let remainder = len % 64;
252        let chunks = self.chunks();
253
254        for (chunk_idx, src_chunk) in chunks.iter().enumerate() {
255            let packed = collect_bool_word(64, |bit_idx| {
256                let i = bit_idx + chunk_idx * 64;
257                let bit_value = (src_chunk >> bit_idx) & 1 == 1;
258                f(i, bit_value)
259            });
260
261            // SAFETY: Already allocated sufficient capacity
262            unsafe { buffer.push_unchecked(packed) }
263        }
264
265        if remainder != 0 {
266            let src_chunk = chunks.remainder_bits();
267            let packed = collect_bool_word(remainder, |bit_idx| {
268                let i = bit_idx + chunks_count * 64;
269                let bit_value = (src_chunk >> bit_idx) & 1 == 1;
270                f(i, bit_value)
271            });
272
273            // SAFETY: Already allocated sufficient capacity
274            unsafe { buffer.push_unchecked(packed) }
275        }
276
277        let mut bytes = buffer.into_byte_buffer();
278        bytes.truncate(len.div_ceil(8));
279
280        Self {
281            buffer: bytes.freeze(),
282            offset: 0,
283            len,
284        }
285    }
286
287    /// Clear all bits in the buffer, preserving existing capacity.
288    #[inline]
289    pub fn clear(&mut self) {
290        self.buffer.clear();
291        self.len = 0;
292        self.offset = 0;
293    }
294
295    /// Get the logical length of this `BoolBuffer`.
296    ///
297    /// This may differ from the physical length of the backing buffer, for example if it was
298    /// created using the `new_with_offset` constructor, or if it was sliced.
299    #[inline]
300    pub fn len(&self) -> usize {
301        self.len
302    }
303
304    /// Returns `true` if the `BoolBuffer` is empty.
305    #[inline]
306    pub fn is_empty(&self) -> bool {
307        self.len() == 0
308    }
309
310    /// Offset of the start of the buffer in bits.
311    #[allow(clippy::inline_always)]
312    #[inline(always)]
313    pub fn offset(&self) -> usize {
314        self.offset
315    }
316
317    /// Get a reference to the underlying buffer.
318    #[allow(clippy::inline_always)]
319    #[inline(always)]
320    pub fn inner(&self) -> &ByteBuffer {
321        &self.buffer
322    }
323
324    /// Return the backing bytes for this bit buffer when its logical offset is byte-aligned.
325    ///
326    /// The returned slice contains exactly `self.len().div_ceil(8)` bytes. Bits past the logical
327    /// length in the final byte are outside the buffer's logical range and should be ignored by
328    /// callers.
329    #[inline]
330    pub fn byte_aligned_bytes(&self) -> Option<&[u8]> {
331        if !self.offset.is_multiple_of(8) {
332            return None;
333        }
334
335        let n_bytes = self.len.div_ceil(8);
336        let start = self.offset / 8;
337        let end = start + n_bytes;
338        Some(&self.buffer.as_slice()[start..end])
339    }
340
341    /// Retrieve the value at the given index.
342    ///
343    /// Panics if the index is out of bounds.
344    ///
345    /// Please note for repeatedly calling this function, please prefer [`crate::get_bit`].
346    #[inline]
347    pub fn value(&self, index: usize) -> bool {
348        assert!(index < self.len);
349        unsafe { self.value_unchecked(index) }
350    }
351
352    /// Retrieve the value at the given index without bounds checking
353    ///
354    /// # SAFETY
355    /// Caller must ensure that index is within the range of the buffer
356    #[inline]
357    pub unsafe fn value_unchecked(&self, index: usize) -> bool {
358        unsafe { get_bit_unchecked(self.buffer.as_ptr(), index + self.offset) }
359    }
360
361    /// Create a new zero-copy slice of this BoolBuffer that begins at the `start` index and extends
362    /// for `len` bits.
363    ///
364    /// Panics if the slice would extend beyond the end of the buffer.
365    #[inline]
366    pub fn slice(&self, range: impl RangeBounds<usize>) -> Self {
367        let (byte_offset, meta) = BitBufferMeta::new(self.offset, self.len).slice(range);
368
369        // Trim whole bytes off the front directly rather than going through `new_with_offset`,
370        // which would slice (and re-clone) the clone we'd have to pass it.
371        let buffer = if byte_offset != 0 {
372            self.buffer.slice_unaligned(byte_offset..)
373        } else {
374            self.buffer.clone().aligned(Alignment::none())
375        };
376
377        Self {
378            buffer,
379            offset: meta.offset(),
380            len: meta.len(),
381        }
382    }
383
384    /// Slice any full bytes from the buffer, leaving the offset < 8.
385    pub fn shrink_offset(self) -> Self {
386        let word_start = self.offset / 8;
387        let word_end = (self.offset + self.len).div_ceil(8);
388
389        let buffer = self.buffer.slice(word_start..word_end);
390
391        let bit_offset = self.offset % 8;
392        let len = self.len;
393        BitBuffer::new_with_offset(buffer, len, bit_offset)
394    }
395
396    /// Access chunks of the buffer aligned to 8 byte boundary as [prefix, \<full chunks\>, suffix]
397    #[inline]
398    pub fn unaligned_chunks(&self) -> UnalignedBitChunk<'_> {
399        UnalignedBitChunk::new(self.buffer.as_slice(), self.offset, self.len)
400    }
401
402    /// Access chunks of the underlying buffer as 8 byte chunks with a final trailer
403    ///
404    /// If you're performing operations on a single buffer, prefer [BitBuffer::unaligned_chunks]
405    #[inline]
406    pub fn chunks(&self) -> BitChunks<'_> {
407        BitChunks::new(self.buffer.as_slice(), self.offset, self.len)
408    }
409
410    /// Get the number of set bits in the buffer.
411    #[inline]
412    pub fn true_count(&self) -> usize {
413        count_ones(self.buffer.as_slice(), self.offset, self.len)
414    }
415
416    /// Get the number of set bits in the bit range `[start, end)`.
417    ///
418    /// Unlike `self.slice(start..end).true_count()`, this counts directly over the
419    /// existing backing buffer without allocating or cloning a new [`BitBuffer`],
420    /// making it cheap to call repeatedly over many small ranges.
421    ///
422    /// Panics if `start > end` or `end > len`.
423    #[inline]
424    pub fn count_range(&self, start: usize, end: usize) -> usize {
425        assert!(start <= end, "start {start} exceeds end {end}");
426        assert!(end <= self.len, "end {end} exceeds len {}", self.len);
427        count_ones(self.buffer.as_slice(), self.offset + start, end - start)
428    }
429
430    /// Returns the position of the `nth` set bit (0-indexed).
431    ///
432    /// This is the "select" operation on a bitmap: given a rank `nth`, find
433    /// which logical bit position holds that rank.
434    ///
435    /// Returns `None` if `nth` is greater than or equal to the number of set bits.
436    #[inline]
437    pub fn select(&self, nth: usize) -> Option<usize> {
438        bit_select(self.buffer.as_slice(), self.offset, self.len, nth)
439    }
440
441    /// Returns the index of the last set bit, or `None` if every bit is unset.
442    ///
443    /// This scans from the end a word at a time, avoiding the full forward scan required by
444    /// [`Self::select`] when selecting the final set bit.
445    #[inline]
446    pub fn last_set_index(&self) -> Option<usize> {
447        let chunks = self.unaligned_chunks();
448        let lead = chunks.lead_padding();
449        let prefix_words = usize::from(chunks.prefix().is_some());
450
451        if let Some(word) = chunks.suffix()
452            && word != 0
453        {
454            let word_index = prefix_words + chunks.chunks().len();
455            return Some(word_index * 64 + 63 - word.leading_zeros() as usize - lead);
456        }
457
458        for (index, &word) in chunks.chunks().iter().enumerate().rev() {
459            if word != 0 {
460                let word_index = prefix_words + index;
461                return Some(word_index * 64 + 63 - word.leading_zeros() as usize - lead);
462            }
463        }
464
465        chunks.prefix().filter(|word| *word != 0).map(|word| {
466            debug_assert!(word.trailing_zeros() as usize >= lead);
467            63 - word.leading_zeros() as usize - lead
468        })
469    }
470
471    /// Get the number of unset bits in the buffer.
472    #[inline]
473    pub fn false_count(&self) -> usize {
474        self.len - self.true_count()
475    }
476
477    /// Iterator over bits in the buffer
478    #[inline]
479    pub fn iter(&self) -> BitIterator<'_> {
480        BitIterator::new(self.buffer.as_slice(), self.offset, self.len)
481    }
482
483    /// Iterator over set indices of the underlying buffer
484    #[inline]
485    pub fn set_indices(&self) -> BitIndexIterator<'_> {
486        BitIndexIterator::new(self.buffer.as_slice(), self.offset, self.len)
487    }
488
489    /// Iterator over set slices of the underlying buffer
490    #[inline]
491    pub fn set_slices(&self) -> BitSliceIterator<'_> {
492        BitSliceIterator::new(self.buffer.as_slice(), self.offset, self.len)
493    }
494
495    /// Invoke `f(index)` for every set bit, in ascending order, processing a `u64`
496    /// word at a time.
497    ///
498    /// This is the fast way to "do something for each set bit": it skips all-zero
499    /// words, fast-paths all-one words, and walks the remaining bits with
500    /// `trailing_zeros`. Prefer it over `for i in 0..len { if buf.value(i) { f(i) } }`
501    /// (which pays a branch per element) and over collecting [`Self::set_indices`]
502    /// (whose per-`next` iterator state does not inline as well).
503    #[inline]
504    pub fn for_each_set_index<F: FnMut(usize)>(&self, mut f: F) {
505        let Ok(()) = self.try_for_each_set_index(|index| {
506            f(index);
507            Ok::<_, Infallible>(())
508        });
509    }
510
511    /// Fallible variant of [`for_each_set_index`](Self::for_each_set_index).
512    ///
513    /// Stops and returns the first error from `f`.
514    #[inline]
515    pub fn try_for_each_set_index<E, F>(&self, mut f: F) -> Result<(), E>
516    where
517        F: FnMut(usize) -> Result<(), E>,
518    {
519        let mut base = 0usize;
520        for word in self.chunks().iter_padded() {
521            if word == u64::MAX {
522                for k in 0..64 {
523                    f(base + k)?;
524                }
525            } else {
526                let mut w = word;
527                while w != 0 {
528                    f(base + w.trailing_zeros() as usize)?;
529                    w &= w - 1;
530                }
531            }
532            base += 64;
533        }
534
535        Ok(())
536    }
537
538    /// Created a new BitBuffer with offset reset to 0
539    pub fn sliced(&self) -> Self {
540        if self.offset.is_multiple_of(8) {
541            return Self::new(
542                self.buffer
543                    .slice(self.offset / 8..(self.offset + self.len).div_ceil(8)),
544                self.len,
545            );
546        }
547
548        // Allocate directly rather than clone + identity op which would fail try_into_mut.
549        bitwise_unary_op_copy(self, |a| a)
550    }
551}
552
553// Conversions
554
555impl BitBuffer {
556    /// Returns the offset, len and underlying buffer.
557    #[inline]
558    pub fn into_inner(self) -> (usize, usize, ByteBuffer) {
559        (self.offset, self.len, self.buffer)
560    }
561
562    /// Attempt to convert this `BitBuffer` into a mutable version.
563    #[inline]
564    pub fn try_into_mut(self) -> Result<BitBufferMut, Self> {
565        match self.buffer.try_into_mut() {
566            Ok(buffer) => Ok(BitBufferMut::from_buffer(buffer, self.offset, self.len)),
567            Err(buffer) => Err(BitBuffer::new_with_offset(buffer, self.len, self.offset)),
568        }
569    }
570}
571
572impl From<&[bool]> for BitBuffer {
573    fn from(value: &[bool]) -> Self {
574        BitBufferMut::from(value).freeze()
575    }
576}
577
578impl From<Vec<bool>> for BitBuffer {
579    fn from(value: Vec<bool>) -> Self {
580        BitBufferMut::from(value).freeze()
581    }
582}
583
584impl FromIterator<bool> for BitBuffer {
585    #[inline]
586    fn from_iter<T: IntoIterator<Item = bool>>(iter: T) -> Self {
587        BitBufferMut::from_iter(iter).freeze()
588    }
589}
590
591impl BitOr for BitBuffer {
592    type Output = Self;
593
594    #[inline]
595    fn bitor(self, rhs: Self) -> Self::Output {
596        bitwise_binary_op_lhs_owned(self, &rhs, |a, b| a | b)
597    }
598}
599
600impl BitOr for &BitBuffer {
601    type Output = BitBuffer;
602
603    #[inline]
604    fn bitor(self, rhs: Self) -> Self::Output {
605        bitwise_binary_op(self, rhs, |a, b| a | b)
606    }
607}
608
609impl BitOr<&BitBuffer> for BitBuffer {
610    type Output = BitBuffer;
611
612    #[inline]
613    fn bitor(self, rhs: &BitBuffer) -> Self::Output {
614        bitwise_binary_op_lhs_owned(self, rhs, |a, b| a | b)
615    }
616}
617
618impl BitAnd for &BitBuffer {
619    type Output = BitBuffer;
620
621    #[inline]
622    fn bitand(self, rhs: Self) -> Self::Output {
623        bitwise_binary_op(self, rhs, |a, b| a & b)
624    }
625}
626
627impl BitAnd<BitBuffer> for &BitBuffer {
628    type Output = BitBuffer;
629
630    #[inline]
631    fn bitand(self, rhs: BitBuffer) -> Self::Output {
632        self.bitand(&rhs)
633    }
634}
635
636impl BitAnd<&BitBuffer> for BitBuffer {
637    type Output = BitBuffer;
638
639    #[inline]
640    fn bitand(self, rhs: &BitBuffer) -> Self::Output {
641        bitwise_binary_op_lhs_owned(self, rhs, |a, b| a & b)
642    }
643}
644
645impl BitAnd<BitBuffer> for BitBuffer {
646    type Output = BitBuffer;
647
648    #[inline]
649    fn bitand(self, rhs: BitBuffer) -> Self::Output {
650        bitwise_binary_op_lhs_owned(self, &rhs, |a, b| a & b)
651    }
652}
653
654impl Not for &BitBuffer {
655    type Output = BitBuffer;
656
657    #[inline]
658    fn not(self) -> Self::Output {
659        // Allocate directly rather than clone+try_into_mut, which always fails
660        // since the clone shares the Arc with the original reference.
661        bitwise_unary_op_copy(self, |a| !a)
662    }
663}
664
665impl Not for BitBuffer {
666    type Output = BitBuffer;
667
668    #[inline]
669    fn not(self) -> Self::Output {
670        bitwise_unary_op(self, |a| !a)
671    }
672}
673
674impl BitXor for &BitBuffer {
675    type Output = BitBuffer;
676
677    #[inline]
678    fn bitxor(self, rhs: Self) -> Self::Output {
679        bitwise_binary_op(self, rhs, |a, b| a ^ b)
680    }
681}
682
683impl BitXor<&BitBuffer> for BitBuffer {
684    type Output = BitBuffer;
685
686    #[inline]
687    fn bitxor(self, rhs: &BitBuffer) -> Self::Output {
688        bitwise_binary_op_lhs_owned(self, rhs, |a, b| a ^ b)
689    }
690}
691
692impl BitBuffer {
693    /// Create a new BitBuffer by performing a bitwise AND NOT operation between two BitBuffers.
694    ///
695    /// This operation is sufficiently common that we provide a dedicated method for it avoid
696    /// making two passes over the data.
697    pub fn bitand_not(&self, rhs: &BitBuffer) -> BitBuffer {
698        bitwise_binary_op(self, rhs, |a, b| a & !b)
699    }
700
701    /// Owned variant of [`bitand_not`](Self::bitand_not) that can mutate in-place when possible.
702    pub fn into_bitand_not(self, rhs: &BitBuffer) -> BitBuffer {
703        bitwise_binary_op_lhs_owned(self, rhs, |a, b| a & !b)
704    }
705
706    /// Iterate through bits in a buffer.
707    ///
708    /// # Arguments
709    ///
710    /// * `f` - Callback function taking (bit_index, is_set)
711    ///
712    /// # Panics
713    ///
714    /// Panics if the range is outside valid bounds of the buffer.
715    #[inline]
716    pub fn iter_bits<F>(&self, mut f: F)
717    where
718        F: FnMut(usize, bool),
719    {
720        let total_bits = self.len;
721        if total_bits == 0 {
722            return;
723        }
724
725        // Process in 64-bit chunks for better ILP and fewer loop iterations.
726        let chunks = self.chunks();
727        let chunks_count = total_bits / 64;
728        let remainder = total_bits % 64;
729
730        for (chunk_idx, chunk) in chunks.iter().enumerate() {
731            let base = chunk_idx * 64;
732            for bit_idx in 0..64 {
733                f(base + bit_idx, (chunk >> bit_idx) & 1 == 1);
734            }
735        }
736
737        if remainder != 0 {
738            let rem_chunk = chunks.remainder_bits();
739            let base = chunks_count * 64;
740            for bit_idx in 0..remainder {
741                f(base + bit_idx, (rem_chunk >> bit_idx) & 1 == 1);
742            }
743        }
744    }
745}
746
747impl<'a> IntoIterator for &'a BitBuffer {
748    type Item = bool;
749    type IntoIter = BitIterator<'a>;
750
751    fn into_iter(self) -> Self::IntoIter {
752        self.iter()
753    }
754}
755
756#[cfg(test)]
757mod tests {
758    use rstest::rstest;
759
760    use crate::ByteBuffer;
761    use crate::bit::BitBuffer;
762    use crate::buffer;
763
764    #[test]
765    fn test_bool() {
766        // Create a new Buffer<u64> of length 1024 where the 8th bit is set.
767        let buffer: ByteBuffer = buffer![1 << 7; 1024];
768        let bools = BitBuffer::new(buffer, 1024 * 8);
769
770        // sanity checks
771        assert_eq!(bools.len(), 1024 * 8);
772        assert!(!bools.is_empty());
773        assert_eq!(bools.true_count(), 1024);
774        assert_eq!(bools.false_count(), 1024 * 7);
775
776        // Check all the values
777        for word in 0..1024 {
778            for bit in 0..8 {
779                if bit == 7 {
780                    assert!(bools.value(word * 8 + bit));
781                } else {
782                    assert!(!bools.value(word * 8 + bit));
783                }
784            }
785        }
786
787        // Slice the buffer to create a new subset view.
788        let sliced = bools.slice(64..72);
789
790        // sanity checks
791        assert_eq!(sliced.len(), 8);
792        assert!(!sliced.is_empty());
793        assert_eq!(sliced.true_count(), 1);
794        assert_eq!(sliced.false_count(), 7);
795
796        // Check all of the values like before
797        for bit in 0..8 {
798            if bit == 7 {
799                assert!(sliced.value(bit));
800            } else {
801                assert!(!sliced.value(bit));
802            }
803        }
804    }
805
806    #[test]
807    fn test_padded_equaltiy() {
808        let buf1 = BitBuffer::new_set(64); // All bits set.
809        let buf2 = BitBuffer::collect_bool(64, |x| x < 32); // First half set, other half unset.
810
811        for i in 0..32 {
812            assert_eq!(buf1.value(i), buf2.value(i), "Bit {} should be the same", i);
813        }
814
815        for i in 32..64 {
816            assert_ne!(buf1.value(i), buf2.value(i), "Bit {} should differ", i);
817        }
818
819        assert_eq!(
820            buf1.slice(0..32),
821            buf2.slice(0..32),
822            "Buffer slices with same bits should be equal (`PartialEq` needs `iter_padded()`)"
823        );
824        assert_ne!(
825            buf1.slice(32..64),
826            buf2.slice(32..64),
827            "Buffer slices with different bits should not be equal (`PartialEq` needs `iter_padded()`)"
828        );
829    }
830
831    #[test]
832    fn test_slice_offset_calculation() {
833        let buf = BitBuffer::collect_bool(16, |_| true);
834        let sliced = buf.slice(10..16);
835        assert_eq!(sliced.len(), 6);
836        // Ensure the offset is modulo 8
837        assert_eq!(sliced.offset(), 2);
838    }
839
840    #[test]
841    fn test_byte_aligned_bytes() {
842        let bytes: ByteBuffer = buffer![0b1010_0101u8, 0b0000_0011];
843        let buf = BitBuffer::new(bytes.clone(), 10);
844        assert_eq!(buf.byte_aligned_bytes(), Some(bytes.as_slice()));
845
846        let byte_sliced = buf.slice(8..10);
847        assert_eq!(byte_sliced.byte_aligned_bytes(), Some(&[0b0000_0011][..]));
848
849        let bit_sliced = buf.slice(1..9);
850        assert!(bit_sliced.byte_aligned_bytes().is_none());
851    }
852
853    #[test]
854    fn test_from_indices_dense_crosses_words() {
855        let len = 130;
856        let indices = (0..len).filter(|idx| idx % 3 != 1);
857        let buf = BitBuffer::from_indices(len, indices);
858
859        assert_eq!(buf.len(), len);
860        for idx in 0..len {
861            assert_eq!(buf.value(idx), idx % 3 != 1, "mismatch at {idx}");
862        }
863    }
864
865    #[test]
866    #[should_panic(expected = "index 5 exceeds len 5")]
867    fn test_from_indices_out_of_bounds() {
868        BitBuffer::from_indices(5, [0, 5]);
869    }
870
871    #[rstest]
872    #[case(0, 0, None)]
873    #[case(3, 7, None)]
874    #[case(0, 1, Some(0))]
875    #[case(8, 64, Some(63))]
876    #[case(13, 65, Some(0))]
877    #[case(13, 65, Some(64))]
878    #[case(67, 151, Some(97))]
879    #[case(67, 151, Some(150))]
880    fn last_set_index_handles_offsets_and_padding(
881        #[case] offset: usize,
882        #[case] len: usize,
883        #[case] expected: Option<usize>,
884    ) {
885        let backing = BitBuffer::from_iter(
886            std::iter::repeat_n(true, offset)
887                .chain((0..len).map(|index| Some(index) == expected))
888                .chain(std::iter::repeat_n(true, 7)),
889        );
890        let buffer = BitBuffer::new_with_offset(backing.inner().clone(), len, offset);
891
892        assert_eq!(buffer.last_set_index(), expected);
893    }
894
895    #[rstest]
896    #[case(5)]
897    #[case(8)]
898    #[case(10)]
899    #[case(13)]
900    #[case(16)]
901    #[case(23)]
902    #[case(100)]
903    fn test_iter_bits(#[case] len: usize) {
904        let buf = BitBuffer::collect_bool(len, |i| i % 2 == 0);
905
906        let mut collected = Vec::new();
907        buf.iter_bits(|idx, is_set| {
908            collected.push((idx, is_set));
909        });
910
911        assert_eq!(collected.len(), len);
912
913        for (idx, is_set) in collected {
914            assert_eq!(is_set, idx % 2 == 0);
915        }
916    }
917
918    #[rstest]
919    #[case(3, 5)]
920    #[case(3, 8)]
921    #[case(5, 10)]
922    #[case(2, 16)]
923    #[case(8, 16)]
924    #[case(9, 16)]
925    #[case(17, 16)]
926    fn test_iter_bits_with_offset(#[case] offset: usize, #[case] len: usize) {
927        let total_bits = offset + len;
928        let buf = BitBuffer::collect_bool(total_bits, |i| i % 2 == 0);
929        let buf_with_offset = BitBuffer::new_with_offset(buf.inner().clone(), len, offset);
930
931        let mut collected = Vec::new();
932        buf_with_offset.iter_bits(|idx, is_set| {
933            collected.push((idx, is_set));
934        });
935
936        assert_eq!(collected.len(), len);
937
938        for (idx, is_set) in collected {
939            // The bits should match the original buffer at positions offset + idx
940            assert_eq!(is_set, (offset + idx).is_multiple_of(2));
941        }
942    }
943
944    #[rstest]
945    #[case(8, 10)]
946    #[case(9, 7)]
947    #[case(16, 8)]
948    #[case(17, 10)]
949    fn test_iter_bits_catches_wrong_byte_offset(#[case] offset: usize, #[case] len: usize) {
950        let total_bits = offset + len;
951        // Alternating pattern to catch byte offset errors: Bits are set for even indexed bytes.
952        let buf = BitBuffer::collect_bool(total_bits, |i| (i / 8) % 2 == 0);
953
954        let buf_with_offset = BitBuffer::new_with_offset(buf.inner().clone(), len, offset);
955
956        let mut collected = Vec::new();
957        buf_with_offset.iter_bits(|idx, is_set| {
958            collected.push((idx, is_set));
959        });
960
961        assert_eq!(collected.len(), len);
962
963        for (idx, is_set) in collected {
964            let bit_position = offset + idx;
965            let byte_index = bit_position / 8;
966            let expected_is_set = byte_index.is_multiple_of(2);
967
968            assert_eq!(
969                is_set, expected_is_set,
970                "Bit mismatch at index {}: expected {} got {}",
971                bit_position, expected_is_set, is_set
972            );
973        }
974    }
975
976    #[rstest]
977    #[case(5)]
978    #[case(8)]
979    #[case(10)]
980    #[case(64)]
981    #[case(65)]
982    #[case(100)]
983    #[case(128)]
984    fn test_map_cmp_identity(#[case] len: usize) {
985        // map_cmp with identity function should return the same buffer
986        let buf = BitBuffer::collect_bool(len, |i| i % 3 == 0);
987        let mapped = buf.map_cmp(|_idx, bit| bit);
988
989        assert_eq!(buf.len(), mapped.len());
990        for i in 0..len {
991            assert_eq!(buf.value(i), mapped.value(i), "Mismatch at index {}", i);
992        }
993    }
994
995    #[rstest]
996    #[case(5)]
997    #[case(8)]
998    #[case(64)]
999    #[case(65)]
1000    #[case(100)]
1001    fn test_map_cmp_negate(#[case] len: usize) {
1002        // map_cmp negating all bits
1003        let buf = BitBuffer::collect_bool(len, |i| i % 2 == 0);
1004        let mapped = buf.map_cmp(|_idx, bit| !bit);
1005
1006        assert_eq!(buf.len(), mapped.len());
1007        for i in 0..len {
1008            assert_eq!(!buf.value(i), mapped.value(i), "Mismatch at index {}", i);
1009        }
1010    }
1011
1012    #[rstest]
1013    #[case(0, 0)]
1014    #[case(0, 64)]
1015    #[case(5, 70)]
1016    #[case(64, 130)]
1017    #[case(0, 200)]
1018    fn test_count_range(#[case] start: usize, #[case] end: usize) {
1019        let len = 200;
1020        let buf = BitBuffer::collect_bool(len, |i| i % 3 == 0);
1021        let expected = (start..end).filter(|i| i % 3 == 0).count();
1022        assert_eq!(buf.count_range(start, end), expected);
1023        // Must agree with slicing then counting.
1024        assert_eq!(
1025            buf.count_range(start, end),
1026            buf.slice(start..end).true_count()
1027        );
1028    }
1029
1030    #[rstest]
1031    #[case(3)]
1032    #[case(7)]
1033    fn test_count_range_with_offset(#[case] offset: usize) {
1034        let len = 150;
1035        let buf = BitBuffer::collect_bool(offset + len, |i| i % 2 == 0);
1036        let view = BitBuffer::new_with_offset(buf.inner().clone(), len, offset);
1037        for (start, end) in [(0, len), (10, 100), (1, 2), (63, 129)] {
1038            let expected = (offset + start..offset + end)
1039                .filter(|i| i % 2 == 0)
1040                .count();
1041            assert_eq!(view.count_range(start, end), expected, "[{start}, {end})");
1042        }
1043    }
1044
1045    #[rstest]
1046    #[case(0)]
1047    #[case(1)]
1048    #[case(63)]
1049    #[case(64)]
1050    #[case(65)]
1051    #[case(200)]
1052    #[case(1000)]
1053    fn test_set_index_visitors_match_set_indices(#[case] len: usize) {
1054        let buf = BitBuffer::collect_bool(len, |i| i % 5 == 0 || i % 7 == 0);
1055        let expected: Vec<usize> = buf.set_indices().collect();
1056
1057        let mut got = Vec::new();
1058        buf.for_each_set_index(|i| got.push(i));
1059        assert_eq!(got, expected);
1060
1061        let mut fallible_got = Vec::new();
1062        let result = buf.try_for_each_set_index(|i| {
1063            fallible_got.push(i);
1064            Ok::<(), ()>(())
1065        });
1066        assert_eq!(result, Ok(()));
1067        assert_eq!(fallible_got, expected);
1068    }
1069
1070    #[rstest]
1071    #[case(3, 200)]
1072    #[case(7, 130)]
1073    fn test_for_each_set_index_with_offset(#[case] offset: usize, #[case] len: usize) {
1074        let base = BitBuffer::collect_bool(offset + len, |i| i % 3 == 0);
1075        let view = BitBuffer::new_with_offset(base.inner().clone(), len, offset);
1076        let expected: Vec<usize> = view.set_indices().collect();
1077        let mut got = Vec::new();
1078        view.for_each_set_index(|i| got.push(i));
1079        assert_eq!(got, expected);
1080    }
1081
1082    #[test]
1083    fn test_for_each_set_index_all_set() {
1084        let buf = BitBuffer::new_set(130);
1085        let mut got = Vec::new();
1086        buf.for_each_set_index(|i| got.push(i));
1087        assert_eq!(got, (0..130).collect::<Vec<_>>());
1088    }
1089
1090    #[test]
1091    fn test_try_for_each_set_index_stops_on_error() {
1092        for (buffer, stop) in [
1093            (BitBuffer::new_set(130), 65),
1094            (BitBuffer::collect_bool(130, |i| i % 3 == 0), 66),
1095        ] {
1096            let mut visited = Vec::new();
1097            let result = buffer.try_for_each_set_index(|index| {
1098                visited.push(index);
1099                if index == stop {
1100                    return Err(index);
1101                }
1102
1103                Ok(())
1104            });
1105
1106            assert_eq!(result, Err(stop));
1107            assert_eq!(
1108                visited,
1109                buffer
1110                    .set_indices()
1111                    .take_while(|&i| i <= stop)
1112                    .collect::<Vec<_>>()
1113            );
1114        }
1115    }
1116
1117    #[test]
1118    fn test_map_cmp_conditional() {
1119        // map_cmp with conditional logic based on index and bit value
1120        let len = 100;
1121        let buf = BitBuffer::collect_bool(len, |i| i % 2 == 0);
1122
1123        // Only keep bits that are set AND at even index divisible by 4
1124        let mapped = buf.map_cmp(|idx, bit| bit && idx % 4 == 0);
1125
1126        for i in 0..len {
1127            let expected = (i % 2 == 0) && (i % 4 == 0);
1128            assert_eq!(mapped.value(i), expected, "Mismatch at index {}", i);
1129        }
1130    }
1131}