1+ namespace Genbox . FastData . Internal . Misc ;
2+
3+ /// <summary>This class starts out as a bitset, but when too many items are added, it switches to a HashSet</summary>
4+ internal sealed class SwitchingBitSet
5+ {
6+ private readonly int _maxBitSetWords ;
7+ private readonly bool _offByOneMode ; // "Off by one mode" is needed for length bitarrays as we occupy the first bit as length = 1
8+ private ulong [ ] ? _bits ;
9+ private HashSet < int > ? _set ;
10+
11+ internal SwitchingBitSet ( int length , bool offByOneMode , int maxBitSetWords = 131072 )
12+ {
13+ if ( length <= 0 )
14+ throw new InvalidOperationException ( "Length must be greater than zero." ) ;
15+
16+ _offByOneMode = offByOneMode ;
17+ _maxBitSetWords = maxBitSetWords ;
18+
19+ int wordLength = GetWordLength ( length ) ;
20+
21+ if ( wordLength <= maxBitSetWords )
22+ _bits = new ulong [ wordLength ] ;
23+ else
24+ _set = new HashSet < int > ( ) ;
25+ }
26+
27+ internal bool IsBitSet => _bits != null ;
28+ internal ulong [ ] BitSet => _bits ?? [ ] ;
29+
30+ internal bool Add ( int index )
31+ {
32+ if ( index < 0 )
33+ throw new ArgumentException ( "Index must be non-negative: " + index , nameof ( index ) ) ;
34+
35+ if ( _bits != null )
36+ {
37+ EnsureCapacityForIndex ( index ) ;
38+
39+ if ( _bits == null )
40+ return _set ! . Add ( index ) ;
41+
42+ GetPosition ( index , out int wordIndex , out ulong mask ) ;
43+
44+ if ( ( _bits [ wordIndex ] & mask ) != 0 )
45+ return false ;
46+
47+ _bits [ wordIndex ] |= mask ;
48+ return true ;
49+ }
50+
51+ return _set ! . Add ( index ) ;
52+ }
53+
54+ internal bool Contains ( int index )
55+ {
56+ if ( index < 0 )
57+ throw new ArgumentException ( "Index must be non-negative: " + index , nameof ( index ) ) ;
58+
59+ if ( _bits != null )
60+ {
61+ GetPosition ( index , out int wordIndex , out ulong mask ) ;
62+
63+ if ( wordIndex >= _bits . Length )
64+ return false ;
65+
66+ return ( _bits [ wordIndex ] & mask ) != 0 ;
67+ }
68+
69+ return _set ! . Contains ( index ) ;
70+ }
71+
72+ private static int GetWordLength ( int length ) => ( int ) ( ( ( long ) length + 63 ) >> 6 ) ;
73+
74+ private void GetPosition ( int index , out int wordIndex , out ulong mask )
75+ {
76+ wordIndex = index >> 6 ;
77+
78+ if ( _offByOneMode )
79+ {
80+ ulong remainder = ( ulong ) ( index & 63 ) ;
81+ int bitIndex = remainder == 0 ? 63 : ( int ) remainder - 1 ;
82+ mask = 1UL << bitIndex ;
83+ }
84+ else
85+ {
86+ mask = 1UL << ( index & 63 ) ;
87+ }
88+ }
89+
90+ private void SwitchToSet ( )
91+ {
92+ HashSet < int > set = new HashSet < int > ( ) ;
93+
94+ for ( int wordIndex = 0 ; wordIndex < _bits ! . Length ; wordIndex ++ )
95+ {
96+ ulong word = _bits [ wordIndex ] ;
97+ if ( word == 0 )
98+ continue ;
99+
100+ for ( int bitIndex = 0 ; bitIndex < 64 ; bitIndex ++ )
101+ {
102+ if ( ( word & ( 1UL << bitIndex ) ) == 0 )
103+ continue ;
104+
105+ set . Add ( GetIndex ( wordIndex , bitIndex ) ) ;
106+ }
107+ }
108+
109+ _set = set ;
110+ _bits = null ;
111+ }
112+
113+ private int GetIndex ( int wordIndex , int bitIndex )
114+ {
115+ if ( _offByOneMode )
116+ return ( int ) ( ( ( ulong ) wordIndex << 6 ) + ( bitIndex == 63 ? 0UL : ( uint ) bitIndex + 1 ) ) ;
117+
118+ return ( int ) ( ( ( ulong ) wordIndex << 6 ) + ( uint ) bitIndex ) ;
119+ }
120+
121+ private void EnsureCapacityForIndex ( int index )
122+ {
123+ int wordLength = ( index >> 6 ) + 1 ;
124+ if ( wordLength <= _bits ! . Length )
125+ return ;
126+
127+ if ( wordLength > _maxBitSetWords )
128+ {
129+ SwitchToSet ( ) ;
130+ return ;
131+ }
132+
133+ Array . Resize ( ref _bits , wordLength ) ;
134+ }
135+ }
0 commit comments