bitz 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/Gemfile +5 -0
- data/LICENSE +201 -0
- data/README.md +214 -0
- data/Rakefile +10 -0
- data/bitz.gemspec +25 -0
- data/lib/bitz/set.rb +253 -0
- data/lib/bitz/version.rb +3 -0
- data/lib/bitz.rb +4 -0
- data/test/bitz_set_test.rb +722 -0
- data/test/test_helper.rb +2 -0
- metadata +80 -0
data/lib/bitz/set.rb
ADDED
@@ -0,0 +1,253 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Bitz
|
4
|
+
# A dynamic bitset implementation with efficient bit manipulation operations.
|
5
|
+
# Supports automatic buffer resizing and both mutating and non-mutating operations.
|
6
|
+
class Set
|
7
|
+
# Creates a new bitset with the specified capacity.
|
8
|
+
#
|
9
|
+
# @param bits [Integer] the initial capacity in bits (default: 64)
|
10
|
+
# @param fill [Boolean] whether to initialize with all bits set (default: false)
|
11
|
+
def initialize bits = 64, fill: false
|
12
|
+
# round up to the nearest 8 then get byte count
|
13
|
+
bytes = ((bits + 7) & -8) / 8
|
14
|
+
@fill = fill
|
15
|
+
@buffer = "".b
|
16
|
+
resize bytes, @fill
|
17
|
+
end
|
18
|
+
|
19
|
+
# Sets the bit at the specified position to 1.
|
20
|
+
# Automatically resizes the buffer if necessary.
|
21
|
+
#
|
22
|
+
# @param bit [Integer] the bit position to set (0-indexed)
|
23
|
+
# @return [void]
|
24
|
+
def set bit
|
25
|
+
byte = bit / 8
|
26
|
+
while true
|
27
|
+
if val = @buffer.getbyte(byte)
|
28
|
+
@buffer.setbyte(byte, val | (1 << (bit % 8)))
|
29
|
+
break
|
30
|
+
else
|
31
|
+
resize(@buffer.bytesize * 2, @fill)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Sets the bit at the specified position to 0.
|
37
|
+
# Automatically resizes the buffer if necessary.
|
38
|
+
#
|
39
|
+
# @param bit [Integer] the bit position to unset (0-indexed)
|
40
|
+
# @return [void]
|
41
|
+
def unset bit
|
42
|
+
byte = bit / 8
|
43
|
+
while true
|
44
|
+
if val = @buffer.getbyte(byte)
|
45
|
+
@buffer.setbyte(byte, val & ~(1 << (bit % 8)))
|
46
|
+
break
|
47
|
+
else
|
48
|
+
resize(@buffer.bytesize * 2, @fill)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Checks if the bit at the specified position is set.
|
54
|
+
#
|
55
|
+
# @param bit [Integer] the bit position to check (0-indexed)
|
56
|
+
# @return [Boolean, nil] true if bit is set, false if unset, nil if position doesn't exist
|
57
|
+
def set? bit
|
58
|
+
if val = @buffer.getbyte(bit / 8)
|
59
|
+
0 != val & (1 << (bit % 8))
|
60
|
+
else
|
61
|
+
nil
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Creates a deep copy of the bitset.
|
66
|
+
#
|
67
|
+
# @param _ [Object] unused parameter (required by Ruby's dup mechanism)
|
68
|
+
# @return [void]
|
69
|
+
def initialize_copy _
|
70
|
+
super
|
71
|
+
@buffer = @buffer.dup
|
72
|
+
end
|
73
|
+
|
74
|
+
# Returns the number of bits set to 1 in this bitset.
|
75
|
+
# Uses efficient popcount algorithm for fast bit counting.
|
76
|
+
#
|
77
|
+
# @return [Integer] the number of set bits
|
78
|
+
def count
|
79
|
+
@buffer.each_byte.sum { |byte| popcount(byte) }
|
80
|
+
end
|
81
|
+
|
82
|
+
# Returns the current capacity of the bitset in bits.
|
83
|
+
#
|
84
|
+
# @return [Integer] the total number of bits this bitset can hold
|
85
|
+
def capacity
|
86
|
+
@buffer.bytesize * 8
|
87
|
+
end
|
88
|
+
|
89
|
+
# Sets all bits in the bitset to 1.
|
90
|
+
#
|
91
|
+
# @return [void]
|
92
|
+
def set_all
|
93
|
+
@buffer.bytesize.times { |index| @buffer.setbyte(index, 0xFF) }
|
94
|
+
end
|
95
|
+
|
96
|
+
# Sets all bits in the bitset to 0.
|
97
|
+
#
|
98
|
+
# @return [void]
|
99
|
+
def unset_all
|
100
|
+
@buffer.bytesize.times { |index| @buffer.setbyte(index, 0x00) }
|
101
|
+
end
|
102
|
+
|
103
|
+
# Performs an in-place union with another bitset.
|
104
|
+
# This bitset will contain all bits that are set in either bitset.
|
105
|
+
#
|
106
|
+
# @param other [Bitz::Set] the bitset to union with
|
107
|
+
# @return [self] returns self for method chaining
|
108
|
+
# @raise [ArgumentError] if the bitsets have different capacities
|
109
|
+
def set_union other
|
110
|
+
# Raise exception if capacities don't match
|
111
|
+
if other.capacity != capacity
|
112
|
+
raise ArgumentError, "Cannot union bitsets with different capacities: #{capacity} != #{other.capacity}"
|
113
|
+
end
|
114
|
+
|
115
|
+
# Perform bitwise OR with each byte
|
116
|
+
other_buffer = other.buffer
|
117
|
+
other_buffer.each_byte.with_index do |byte, index|
|
118
|
+
current = @buffer.getbyte(index)
|
119
|
+
@buffer.setbyte(index, current | byte)
|
120
|
+
end
|
121
|
+
|
122
|
+
self
|
123
|
+
end
|
124
|
+
|
125
|
+
# Performs an in-place intersection with another bitset.
|
126
|
+
# This bitset will contain only bits that are set in both bitsets.
|
127
|
+
#
|
128
|
+
# @param other [Bitz::Set] the bitset to intersect with
|
129
|
+
# @return [self] returns self for method chaining
|
130
|
+
# @raise [ArgumentError] if the bitsets have different capacities
|
131
|
+
def set_intersection other
|
132
|
+
# Raise exception if capacities don't match
|
133
|
+
if other.capacity != capacity
|
134
|
+
raise ArgumentError, "Cannot intersect bitsets with different capacities: #{capacity} != #{other.capacity}"
|
135
|
+
end
|
136
|
+
|
137
|
+
# Perform bitwise AND with each byte
|
138
|
+
other_buffer = other.buffer
|
139
|
+
other_buffer.each_byte.with_index do |byte, index|
|
140
|
+
current = @buffer.getbyte(index)
|
141
|
+
@buffer.setbyte(index, current & byte)
|
142
|
+
end
|
143
|
+
|
144
|
+
self
|
145
|
+
end
|
146
|
+
|
147
|
+
# Returns a new bitset containing the intersection of this bitset and another.
|
148
|
+
# Neither original bitset is modified.
|
149
|
+
#
|
150
|
+
# @param other [Bitz::Set] the bitset to intersect with
|
151
|
+
# @return [Bitz::Set] a new bitset with the intersection result
|
152
|
+
# @raise [ArgumentError] if the bitsets have different capacities
|
153
|
+
def & other
|
154
|
+
dup.set_intersection other
|
155
|
+
end
|
156
|
+
|
157
|
+
# Returns a new bitset containing the union of this bitset and another.
|
158
|
+
# Neither original bitset is modified.
|
159
|
+
#
|
160
|
+
# @param other [Bitz::Set] the bitset to union with
|
161
|
+
# @return [Bitz::Set] a new bitset with the union result
|
162
|
+
# @raise [ArgumentError] if the bitsets have different capacities
|
163
|
+
def | other
|
164
|
+
dup.set_union other
|
165
|
+
end
|
166
|
+
|
167
|
+
# Returns a new bitset with all bits flipped (complement/NOT operation).
|
168
|
+
# The original bitset is not modified.
|
169
|
+
#
|
170
|
+
# @return [Bitz::Set] a new bitset with all bits flipped
|
171
|
+
def !
|
172
|
+
dup.toggle_all
|
173
|
+
end
|
174
|
+
|
175
|
+
# Flips all bits in the bitset (complement/NOT operation).
|
176
|
+
# All 0 bits become 1, and all 1 bits become 0.
|
177
|
+
#
|
178
|
+
# @return [self] returns self for method chaining
|
179
|
+
def toggle_all
|
180
|
+
idx = 0
|
181
|
+
@buffer.each_byte do |byte|
|
182
|
+
@buffer.setbyte(idx, ~byte & 0xFF)
|
183
|
+
idx += 1
|
184
|
+
end
|
185
|
+
self
|
186
|
+
end
|
187
|
+
|
188
|
+
# Iterates over each set bit in the bitset, yielding the bit position.
|
189
|
+
# Only bits that are set to 1 are yielded. Bits are yielded in ascending order.
|
190
|
+
# Returns an Enumerator if no block is given.
|
191
|
+
#
|
192
|
+
# @yield [Integer] the position of each set bit (0-indexed)
|
193
|
+
# @return [Enumerator, self] returns Enumerator if no block given, otherwise self
|
194
|
+
# @example
|
195
|
+
# bitset = Bitz::Set.new
|
196
|
+
# bitset.set(2)
|
197
|
+
# bitset.set(5)
|
198
|
+
# bitset.set(10)
|
199
|
+
# bitset.each_bit { |bit| puts bit } # prints 2, 5, 10
|
200
|
+
# bitset.each_bit.to_a # => [2, 5, 10]
|
201
|
+
def each_bit
|
202
|
+
return enum_for(__method__) unless block_given?
|
203
|
+
|
204
|
+
byte = 0
|
205
|
+
@buffer.each_byte { |b|
|
206
|
+
8.times { |bit|
|
207
|
+
if b & 0x1 == 0x1
|
208
|
+
yield byte + bit
|
209
|
+
end
|
210
|
+
b >>= 1
|
211
|
+
}
|
212
|
+
byte += 8
|
213
|
+
}
|
214
|
+
end
|
215
|
+
|
216
|
+
# Compares this bitset with another for equality.
|
217
|
+
# Returns false if the bitsets have different capacities.
|
218
|
+
# Otherwise compares all bytes for exact equality.
|
219
|
+
#
|
220
|
+
# @param other [Object] the object to compare with
|
221
|
+
# @return [Boolean] true if bitsets are equal, false otherwise
|
222
|
+
def == other
|
223
|
+
return false unless other.is_a?(self.class)
|
224
|
+
return false unless other.capacity == capacity
|
225
|
+
|
226
|
+
@buffer == other.buffer
|
227
|
+
end
|
228
|
+
|
229
|
+
alias :eql? :==
|
230
|
+
|
231
|
+
def hash
|
232
|
+
@buffer.hash
|
233
|
+
end
|
234
|
+
|
235
|
+
protected
|
236
|
+
|
237
|
+
attr_reader :buffer
|
238
|
+
|
239
|
+
private
|
240
|
+
|
241
|
+
def resize newlen, fill
|
242
|
+
@buffer << ((fill ? "\xFF" : "\0").b * (newlen - @buffer.bytesize))
|
243
|
+
end
|
244
|
+
|
245
|
+
def popcount n
|
246
|
+
n = n - ((n >> 1) & 0x55) # 01010101 - count adjacent pairs
|
247
|
+
n = (n & 0x33) + ((n >> 2) & 0x33) # 00110011 - count nibbles
|
248
|
+
n = (n + (n >> 4)) & 0x0F # 00001111 - count whole byte
|
249
|
+
n
|
250
|
+
end
|
251
|
+
|
252
|
+
end
|
253
|
+
end
|
data/lib/bitz/version.rb
ADDED
data/lib/bitz.rb
ADDED