@@ -20,6 +20,11 @@ impl BitChunk {
2020 }
2121}
2222
23+ /// Merges a descending stream of distinct one-bit positions (`leading`) with a descending stream
24+ /// of `BitChunk`s (`trailing`) into a single descending `BitChunk` stream. All leading positions
25+ /// must be more significant than all trailing bits, except that the least-significant leading block
26+ /// may overlap the most-significant trailing block (the two are or-ed). Leading positions must be
27+ /// distinct.
2328pub ( crate ) fn iter_bit_chunks (
2429 leading : impl Iterator < Item = usize > ,
2530 trailing : impl Iterator < Item = BitChunk > ,
@@ -55,8 +60,7 @@ impl<I: Iterator<Item = BitChunk>, J: Iterator<Item = usize>> Iterator for BitCh
5560 _ => break ,
5661 }
5762 }
58- // All leading bits were consumed, test whether it can be merged with
59- // trailing bits.
63+ // All leading bits were consumed, test whether it can be merged with trailing bits.
6064 match self . trailing . peek ( ) {
6165 Some ( BitChunk {
6266 index : other_index,
@@ -78,6 +82,39 @@ impl<I: Iterator<Item = BitChunk>, J: Iterator<Item = usize>> Iterator for BitCh
7882 }
7983}
8084
85+ /// Turns a descending stream of one-bit positions into a descending `BitChunk` stream containing
86+ /// each position's parity. Unlike [`iter_bit_chunks`], positions may repeat: a position occurring
87+ /// an even number of times cancels out (xor), and a block that cancels out entirely is skipped.
88+ pub ( crate ) fn parity_bit_positions (
89+ positions : impl Iterator < Item = usize > ,
90+ ) -> impl Iterator < Item = BitChunk > {
91+ ParityBitPositions ( positions. peekable ( ) )
92+ }
93+
94+ struct ParityBitPositions < I : Iterator < Item = usize > > ( Peekable < I > ) ;
95+
96+ impl < I : Iterator < Item = usize > > Iterator for ParityBitPositions < I > {
97+ type Item = BitChunk ;
98+
99+ fn next ( & mut self ) -> Option < Self :: Item > {
100+ loop {
101+ let ( index, bit) = self . 0 . next ( ) ?. into_index_and_bit ( ) ;
102+ let mut block: u64 = bit. into_block ( ) ;
103+ while let Some ( & other) = self . 0 . peek ( ) {
104+ if other. into_index ( ) != index {
105+ break ;
106+ }
107+ block ^= other. into_bit ( ) . into_block ( ) ;
108+ self . 0 . next ( ) ;
109+ }
110+ // The block is empty only if its positions cancelled out entirely; skip it.
111+ if block != 0 {
112+ return Some ( BitChunk :: new ( index, block) ) ;
113+ }
114+ }
115+ }
116+ }
117+
81118/// Combine-s two `BitChunk` iterators using the given operator. Both iterators have to output
82119/// `BitChunk`s from most to least significant and must not report duplicates!
83120struct BinOpBitChunksIterator <
@@ -314,7 +351,7 @@ impl<T: IsBucketType, I: Iterator<Item = BitChunk>> Iterator for BitChunksOnes<T
314351mod tests {
315352 use itertools:: Itertools ;
316353
317- use super :: { iter_ones, BitChunk } ;
354+ use super :: { iter_bit_chunks , iter_ones, parity_bit_positions , BitChunk } ;
318355
319356 #[ test]
320357 fn test_iter_ones ( ) {
@@ -338,4 +375,40 @@ mod tests {
338375 iter_ones:: <usize , _>( chunks. into_iter( ) . peekable( ) ) . collect_vec( )
339376 ) ;
340377 }
378+
379+ #[ test]
380+ fn test_iter_bit_chunks ( ) {
381+ // Distinct leading bits merge within a block (via or) and merge with the trailing block at
382+ // the boundary index.
383+ let chunks = iter_bit_chunks (
384+ vec ! [ 70 , 67 , 5 ] . into_iter ( ) ,
385+ vec ! [ BitChunk :: new( 0 , 1 << 2 ) ] . into_iter ( ) ,
386+ )
387+ . collect_vec ( ) ;
388+ assert_eq ! (
389+ chunks,
390+ vec![
391+ BitChunk :: new( 1 , ( 1 << 6 ) | ( 1 << 3 ) ) , // 70, 67
392+ BitChunk :: new( 0 , ( 1 << 5 ) | ( 1 << 2 ) ) , // 5 (leading) and bit 2 (trailing)
393+ ]
394+ ) ;
395+ }
396+
397+ #[ test]
398+ fn test_parity_bit_positions ( ) {
399+ // Equal positions cancel (xor): 70 twice cancels, 67 stays; 5 three times stays, 3 once.
400+ let chunks = parity_bit_positions ( vec ! [ 70 , 70 , 67 , 5 , 5 , 5 , 3 ] . into_iter ( ) ) . collect_vec ( ) ;
401+ assert_eq ! (
402+ chunks,
403+ vec![
404+ BitChunk :: new( 1 , 1 << 3 ) , // 67
405+ BitChunk :: new( 0 , ( 1 << 5 ) | ( 1 << 3 ) ) , // 5, 3
406+ ]
407+ ) ;
408+
409+ // A block whose positions cancel out entirely is skipped.
410+ assert ! ( parity_bit_positions( vec![ 5 , 5 ] . into_iter( ) )
411+ . collect_vec( )
412+ . is_empty( ) ) ;
413+ }
341414}
0 commit comments