bitz 1.0.0 → 2.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 +4 -4
- data/README.md +3 -3
- data/lib/bitz/set.rb +88 -1
- data/lib/bitz/version.rb +1 -1
- data/test/bitz_set_test.rb +177 -12
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c1e3269dd93770fce52222c370e51af1cc2e4273042a57c3bf5f9414728fe4ed
|
4
|
+
data.tar.gz: 66c36eca3388687c6c6cfdf17c7c65c655f0091ce991eafe3300adc7e8a82696
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cbc7891242609a62513b84274b061ce56d31e35e50b28fadd8eaff9c4e8e54a553e3fa68965cee9afe97f1caff837b949b6d3cdc67ae50067d311c67283eef12
|
7
|
+
data.tar.gz: 10bb83437c2a7a37312c21f459c9326d954358173289cc639c405d5299c965764b5bca5fa7cdb4abf6af978db43a84eea79cf8302d9b43c7d3f69738f7467426
|
data/README.md
CHANGED
@@ -6,7 +6,7 @@ A pure Ruby, JIT-friendly dynamic bitset implementation
|
|
6
6
|
|
7
7
|
- **Dynamic resizing**: Automatically grows buffer as needed when setting bits
|
8
8
|
- **Memory efficient**: Uses packed byte arrays for optimal memory usage
|
9
|
-
- **Ruby-idiomatic**: Standard operators (`&`, `|`,
|
9
|
+
- **Ruby-idiomatic**: Standard operators (`&`, `|`, `~`) and method chaining support
|
10
10
|
- **0-indexed**: Bit positions start from 0, following standard conventions
|
11
11
|
|
12
12
|
## Installation
|
@@ -127,7 +127,7 @@ intersection_result = set1 & set2
|
|
127
127
|
# set1 and set2 are unchanged
|
128
128
|
|
129
129
|
# Complement/NOT (creates new bitset with all bits flipped)
|
130
|
-
complement_result =
|
130
|
+
complement_result = ~set1
|
131
131
|
# complement_result has all bits flipped - bits 0, 3, 4, 5, ..., 63 are set
|
132
132
|
# set1 is unchanged
|
133
133
|
```
|
@@ -174,7 +174,7 @@ copy.set?(5) # => true (copy has original's bits)
|
|
174
174
|
|
175
175
|
- `|(other)` - Union operator (returns new bitset)
|
176
176
|
- `&(other)` - Intersection operator (returns new bitset)
|
177
|
-
-
|
177
|
+
- `~` - Complement/NOT operator (returns new bitset with all bits flipped)
|
178
178
|
|
179
179
|
### Copying
|
180
180
|
|
data/lib/bitz/set.rb
CHANGED
@@ -168,7 +168,7 @@ module Bitz
|
|
168
168
|
# The original bitset is not modified.
|
169
169
|
#
|
170
170
|
# @return [Bitz::Set] a new bitset with all bits flipped
|
171
|
-
def
|
171
|
+
def ~
|
172
172
|
dup.toggle_all
|
173
173
|
end
|
174
174
|
|
@@ -213,6 +213,93 @@ module Bitz
|
|
213
213
|
}
|
214
214
|
end
|
215
215
|
|
216
|
+
# Returns an ASCII art representation of the bitset.
|
217
|
+
# Displays bit indices (in hexadecimal) on top and bit values on bottom in a table format.
|
218
|
+
# Groups bits by bytes (8 bits) with visual separators.
|
219
|
+
#
|
220
|
+
# @param width [Integer] number of bits to display per row (default: 64)
|
221
|
+
# @param start [Integer] starting bit position to display (default: 0)
|
222
|
+
# @return [String] formatted ASCII art table
|
223
|
+
# @example
|
224
|
+
# bitset = Bitz::Set.new(16)
|
225
|
+
# bitset.set(1)
|
226
|
+
# bitset.set(5)
|
227
|
+
# bitset.set(10)
|
228
|
+
# puts bitset.to_ascii(width: 16)
|
229
|
+
# # Output:
|
230
|
+
# # Bit Index: | 0 1 2 3 4 5 6 7 | 8 9 a b c d e f |
|
231
|
+
# # Bit Value: | 0 1 0 0 0 1 0 0 | 0 0 1 0 0 0 0 0 |
|
232
|
+
# # +------------------------+------------------------+
|
233
|
+
def to_ascii width: 64, start: 0
|
234
|
+
lines = []
|
235
|
+
end_bit = [start + width, capacity].min
|
236
|
+
total_bits = end_bit - start
|
237
|
+
|
238
|
+
return "Empty bitset\n" if total_bits <= 0
|
239
|
+
|
240
|
+
# Calculate number of byte groups
|
241
|
+
byte_groups = (total_bits + 7) / 8
|
242
|
+
|
243
|
+
# Build index line
|
244
|
+
index_line = "Bit Index: "
|
245
|
+
value_line = "Bit Value: "
|
246
|
+
border_line = " "
|
247
|
+
|
248
|
+
byte_groups.times do |group|
|
249
|
+
group_start = start + (group * 8)
|
250
|
+
group_end = [group_start + 8, end_bit].min
|
251
|
+
|
252
|
+
index_line += "|"
|
253
|
+
value_line += "|"
|
254
|
+
border_line += "+"
|
255
|
+
|
256
|
+
(group_start...group_end).each do |bit_pos|
|
257
|
+
index_line += sprintf("%3x", bit_pos)
|
258
|
+
value_line += sprintf("%3d", set?(bit_pos) ? 1 : 0)
|
259
|
+
border_line += "---"
|
260
|
+
end
|
261
|
+
|
262
|
+
# Pad incomplete byte groups
|
263
|
+
if group_end - group_start < 8
|
264
|
+
padding = 8 - (group_end - group_start)
|
265
|
+
index_line += " " * padding
|
266
|
+
value_line += " " * padding
|
267
|
+
border_line += "---" * padding
|
268
|
+
end
|
269
|
+
|
270
|
+
index_line += " "
|
271
|
+
value_line += " "
|
272
|
+
border_line += "-"
|
273
|
+
end
|
274
|
+
|
275
|
+
index_line += "|\n"
|
276
|
+
value_line += "|\n"
|
277
|
+
border_line += "+\n"
|
278
|
+
|
279
|
+
lines << index_line
|
280
|
+
lines << value_line
|
281
|
+
lines << border_line
|
282
|
+
|
283
|
+
# Handle multi-row output for large bitsets
|
284
|
+
if end_bit < capacity && width < capacity
|
285
|
+
remaining = capacity - end_bit
|
286
|
+
if remaining > 0
|
287
|
+
lines << to_ascii(width: width, start: end_bit)
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
lines.join
|
292
|
+
end
|
293
|
+
|
294
|
+
# Ruby's pp library integration.
|
295
|
+
# Called by pp when pretty printing this object.
|
296
|
+
#
|
297
|
+
# @param pp [PP] the pretty printer object
|
298
|
+
# @return [void]
|
299
|
+
def pretty_print pp
|
300
|
+
pp.text(to_ascii(width: 32))
|
301
|
+
end
|
302
|
+
|
216
303
|
# Compares this bitset with another for equality.
|
217
304
|
# Returns false if the bitsets have different capacities.
|
218
305
|
# Otherwise compares all bytes for exact equality.
|
data/lib/bitz/version.rb
CHANGED
data/test/bitz_set_test.rb
CHANGED
@@ -411,11 +411,11 @@ class BitzSetTest < Minitest::Test
|
|
411
411
|
assert_equal 3, result.count # bits 1, 2, 3 are set
|
412
412
|
end
|
413
413
|
|
414
|
-
def
|
414
|
+
def test_tilde_basic
|
415
415
|
@set.set(5)
|
416
416
|
@set.set(10)
|
417
417
|
|
418
|
-
result =
|
418
|
+
result = ~@set
|
419
419
|
|
420
420
|
# Original bitset unchanged
|
421
421
|
assert @set.set?(5)
|
@@ -432,8 +432,8 @@ class BitzSetTest < Minitest::Test
|
|
432
432
|
assert_equal 62, result.count # 64 - 2 set bits
|
433
433
|
end
|
434
434
|
|
435
|
-
def
|
436
|
-
result =
|
435
|
+
def test_tilde_empty_set
|
436
|
+
result = ~@set
|
437
437
|
|
438
438
|
# Original bitset unchanged (empty)
|
439
439
|
assert_equal 0, @set.count
|
@@ -443,10 +443,10 @@ class BitzSetTest < Minitest::Test
|
|
443
443
|
assert_equal 64, result.count
|
444
444
|
end
|
445
445
|
|
446
|
-
def
|
446
|
+
def test_tilde_full_set
|
447
447
|
@set.set_all
|
448
448
|
|
449
|
-
result =
|
449
|
+
result = ~@set
|
450
450
|
|
451
451
|
# Original bitset unchanged (full)
|
452
452
|
assert_equal 64, @set.count
|
@@ -456,20 +456,20 @@ class BitzSetTest < Minitest::Test
|
|
456
456
|
assert_equal 0, result.count
|
457
457
|
end
|
458
458
|
|
459
|
-
def
|
460
|
-
result =
|
459
|
+
def test_tilde_returns_new_instance
|
460
|
+
result = ~@set
|
461
461
|
|
462
462
|
refute_same @set, result
|
463
463
|
assert_instance_of Bitz::Set, result
|
464
464
|
assert_equal @set.capacity, result.capacity
|
465
465
|
end
|
466
466
|
|
467
|
-
def
|
467
|
+
def test_tilde_double_negation
|
468
468
|
@set.set(5)
|
469
469
|
@set.set(10)
|
470
470
|
@set.set(20)
|
471
471
|
|
472
|
-
result =
|
472
|
+
result = ~~@set
|
473
473
|
|
474
474
|
# Double negation should restore original
|
475
475
|
assert result.set?(5)
|
@@ -484,12 +484,12 @@ class BitzSetTest < Minitest::Test
|
|
484
484
|
assert_equal 3, @set.count
|
485
485
|
end
|
486
486
|
|
487
|
-
def
|
487
|
+
def test_tilde_specific_capacity
|
488
488
|
small_set = Bitz::Set.new(16)
|
489
489
|
small_set.set(3)
|
490
490
|
small_set.set(7)
|
491
491
|
|
492
|
-
result =
|
492
|
+
result = ~small_set
|
493
493
|
|
494
494
|
# Check capacity preserved
|
495
495
|
assert_equal 16, result.capacity
|
@@ -719,4 +719,169 @@ class BitzSetTest < Minitest::Test
|
|
719
719
|
copy.set(30)
|
720
720
|
refute_equal @set, copy
|
721
721
|
end
|
722
|
+
|
723
|
+
def test_to_ascii_basic
|
724
|
+
@set.set(1)
|
725
|
+
@set.set(5)
|
726
|
+
@set.set(10)
|
727
|
+
|
728
|
+
output = @set.to_ascii(width: 16)
|
729
|
+
|
730
|
+
# Should contain bit indices
|
731
|
+
assert_match(/Bit Index:/, output)
|
732
|
+
assert_match(/Bit Value:/, output)
|
733
|
+
|
734
|
+
# Should contain specific bit positions
|
735
|
+
assert_match(/\s+1\s+/, output)
|
736
|
+
assert_match(/\s+5\s+/, output)
|
737
|
+
assert_match(/\s+10\s+/, output)
|
738
|
+
|
739
|
+
# Should have proper formatting with separators
|
740
|
+
assert_match(/\|/, output)
|
741
|
+
assert_match(/\+/, output)
|
742
|
+
end
|
743
|
+
|
744
|
+
def test_to_ascii_empty_set
|
745
|
+
output = @set.to_ascii(width: 16)
|
746
|
+
|
747
|
+
# Should show all zeros in the value line
|
748
|
+
lines = output.split("\n")
|
749
|
+
value_line = lines.find { |line| line.start_with?("Bit Value:") }
|
750
|
+
|
751
|
+
# Should contain only 0s in the value line (no 1s)
|
752
|
+
assert_match(/0/, value_line)
|
753
|
+
refute_match(/\|\s+[^0\s]/, value_line) # No non-zero values between separators
|
754
|
+
|
755
|
+
assert_match(/Bit Index:/, output)
|
756
|
+
assert_match(/Bit Value:/, output)
|
757
|
+
end
|
758
|
+
|
759
|
+
def test_to_ascii_full_set
|
760
|
+
small_set = Bitz::Set.new(16)
|
761
|
+
small_set.set_all
|
762
|
+
|
763
|
+
output = small_set.to_ascii(width: 16)
|
764
|
+
|
765
|
+
# Should show all ones for a full set
|
766
|
+
lines = output.split("\n")
|
767
|
+
value_line = lines.find { |line| line.start_with?("Bit Value:") }
|
768
|
+
|
769
|
+
# Count the 1s in the value line (look for the pattern " 1")
|
770
|
+
ones_count = value_line.scan(/\s+1(?=\s|\|)/).length
|
771
|
+
assert_equal 16, ones_count
|
772
|
+
end
|
773
|
+
|
774
|
+
def test_to_ascii_custom_width
|
775
|
+
@set.set(2)
|
776
|
+
@set.set(10)
|
777
|
+
@set.set(18)
|
778
|
+
|
779
|
+
# Test with 8-bit width
|
780
|
+
output = @set.to_ascii(width: 8)
|
781
|
+
lines = output.split("\n")
|
782
|
+
|
783
|
+
# Should have multiple rows for bits beyond width
|
784
|
+
assert_operator lines.length, :>, 3
|
785
|
+
|
786
|
+
# First row should contain bits 0-7
|
787
|
+
assert_match(/\s+2\s+/, lines[0])
|
788
|
+
refute_match(/\s+a\s+/, lines[0]) # 10 in hex should not be in first row
|
789
|
+
end
|
790
|
+
|
791
|
+
def test_to_ascii_start_offset
|
792
|
+
@set.set(10)
|
793
|
+
@set.set(15)
|
794
|
+
@set.set(20)
|
795
|
+
|
796
|
+
# Start from bit 8
|
797
|
+
output = @set.to_ascii(width: 16, start: 8)
|
798
|
+
|
799
|
+
# Should contain bits 8-23 (in hex: 8, a=10, f=15, 14=20)
|
800
|
+
assert_match(/\s+a\s+/, output) # 10 in hex
|
801
|
+
assert_match(/\s+f\s+/, output) # 15 in hex
|
802
|
+
assert_match(/\s+14\s+/, output) # 20 in hex
|
803
|
+
|
804
|
+
# Should not contain bits 0-7 in the index line
|
805
|
+
lines = output.split("\n")
|
806
|
+
index_line = lines[0]
|
807
|
+
refute_match(/\|\s+0\s+/, index_line)
|
808
|
+
refute_match(/\|\s+7\s+/, index_line)
|
809
|
+
end
|
810
|
+
|
811
|
+
def test_to_ascii_byte_grouping
|
812
|
+
@set.set(7) # End of first byte
|
813
|
+
@set.set(8) # Start of second byte
|
814
|
+
|
815
|
+
output = @set.to_ascii(width: 16)
|
816
|
+
|
817
|
+
# Should have proper byte separators
|
818
|
+
separators = output.scan(/\|/).length
|
819
|
+
assert_operator separators, :>, 2 # At least opening and closing separators
|
820
|
+
|
821
|
+
# Should group by 8-bit boundaries
|
822
|
+
lines = output.split("\n")
|
823
|
+
index_line = lines[0]
|
824
|
+
|
825
|
+
# Should have bit 7 and 8 in different groups visually
|
826
|
+
assert_match(/\s+7\s+.*\|\s+8\s+/, index_line)
|
827
|
+
end
|
828
|
+
|
829
|
+
def test_to_ascii_large_indices
|
830
|
+
large_set = Bitz::Set.new(128)
|
831
|
+
large_set.set(100)
|
832
|
+
large_set.set(127)
|
833
|
+
|
834
|
+
output = large_set.to_ascii(width: 32, start: 96)
|
835
|
+
|
836
|
+
# Should handle 2-digit hex indices correctly (100 = 0x64, 127 = 0x7f)
|
837
|
+
assert_match(/64/, output) # 100 in hex
|
838
|
+
assert_match(/7f/, output) # 127 in hex
|
839
|
+
|
840
|
+
# Should maintain alignment
|
841
|
+
lines = output.split("\n")
|
842
|
+
index_line = lines[0]
|
843
|
+
value_line = lines[1]
|
844
|
+
|
845
|
+
# Lines should be similar length (within a few chars for borders)
|
846
|
+
assert_in_delta index_line.length, value_line.length, 5
|
847
|
+
end
|
848
|
+
|
849
|
+
def test_to_ascii_edge_cases
|
850
|
+
# Single bit set
|
851
|
+
single_set = Bitz::Set.new(8)
|
852
|
+
single_set.set(0)
|
853
|
+
|
854
|
+
output = single_set.to_ascii(width: 8)
|
855
|
+
assert_match(/Bit Index:/, output)
|
856
|
+
assert_match(/Bit Value:/, output)
|
857
|
+
|
858
|
+
# Empty range
|
859
|
+
empty_output = @set.to_ascii(width: 0)
|
860
|
+
assert_equal "Empty bitset\n", empty_output
|
861
|
+
|
862
|
+
# Start beyond capacity
|
863
|
+
beyond_output = @set.to_ascii(start: 1000)
|
864
|
+
assert_equal "Empty bitset\n", beyond_output
|
865
|
+
end
|
866
|
+
|
867
|
+
def test_pp_integration
|
868
|
+
require 'pp'
|
869
|
+
require 'stringio'
|
870
|
+
|
871
|
+
@set.set(1)
|
872
|
+
@set.set(5)
|
873
|
+
@set.set(10)
|
874
|
+
|
875
|
+
# Capture pp output
|
876
|
+
output = StringIO.new
|
877
|
+
PP.pp(@set, output)
|
878
|
+
result = output.string
|
879
|
+
|
880
|
+
# Should contain the ASCII art format
|
881
|
+
assert_match(/Bit Index:/, result)
|
882
|
+
assert_match(/Bit Value:/, result)
|
883
|
+
assert_match(/\s+1\s+/, result) # 1 in hex is still 1
|
884
|
+
assert_match(/\s+5\s+/, result) # 5 in hex is still 5
|
885
|
+
assert_match(/\s+a\s+/, result) # 10 in hex is 'a'
|
886
|
+
end
|
722
887
|
end
|