/
bit_array.cr
134 lines (121 loc) · 3.75 KB
/
bit_array.cr
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# BitArray is an array data structure that compactly stores bits.
#
# Bits externally represented as `Bool`s are stored internally as
# `UInt32`s. The total number of bits stored is set at creation and is
# immutable.
#
# `BitArray` includes all the methods in `Enumerable`.
#
# ### Example
#
# require "bit_array"
# ba = BitArray.new(12) # => "BitArray[000000000000]"
# ba[2] # => false
# 0.upto(5) { |i| ba[i*2] = true }
# ba # => "BitArray[101010101010]"
# ba[2] # => true
struct BitArray
include Enumerable(Bool)
# The number of bits the BitArray stores
getter size : Int32
# Create a new BitArray of `size` bits.
#
# `initial` optionally sets the starting value, true or false, for all bits
# in the array.
def initialize(@size, initial : Bool = false)
value = initial ? UInt32::MAX : UInt32::MIN
@bits = Pointer(UInt32).malloc(malloc_size, value)
end
# Returns the bit at the given index.
# Negative indices can be used to start counting from the end of the array.
# Raises `IndexError` if trying to access a bit outside the array's range.
#
# ba = BitArray.new(5)
# ba[3] # => false
def [](index)
bit_index, sub_index = bit_index_and_sub_index(index)
(@bits[bit_index] & (1 << sub_index)) > 0
end
# Sets the bit at the given index.
# Negative indices can be used to start counting from the end of the array.
# Raises `IndexError` if trying to access a bit outside the array's range.
#
# ba = BitArray.new(5)
# ba[3] = true
def []=(index, value : Bool)
bit_index, sub_index = bit_index_and_sub_index(index)
if value
@bits[bit_index] |= 1 << sub_index
else
@bits[bit_index] &= ~(1 << sub_index)
end
end
# Toggles the bit at the given index. A false bit becomes a true bit, and
# vice versa.
# Negative indices can be used to start counting from the end of the array.
# Raises `IndexError` if trying to access a bit outside the array's range.
#
# ba = BitArray.new(5)
# ba[3] # => false
# ba.toggle(3)
# ba[3] # => true
def toggle(index)
bit_index, sub_index = bit_index_and_sub_index(index)
@bits[bit_index] ^= 1 << sub_index
end
# Inverts all bits in the array. Falses become true and
# vice versa.
#
# ba = BitArray.new(5)
# ba[2] = true; ba[3] = true
# ba # => BitArray[00110]
# ba.invert
# ba # => BitArray[11001]
def invert
malloc_size.times do |i|
@bits[i] = ~@bits[i]
end
end
# Calls the given block once for each element in self, passing that element as a parameter.
#
# ba = BitArray.new(5)
# ba[2] = true; ba[3] = true
# ba # => BitArray[00110]
# ba.each do |i|
# print i, ", "
# end # => false, false, true, true, false
def each
@size.times do |i|
yield self[i]
end
end
# Creates a string representation of self.
#
# ba = BitArray.new(5)
# puts ba.to_s #=> "BitArray[00000]"
def to_s(io : IO)
io << "BitArray["
each do |value|
io << (value ? "1" : "0")
end
io << "]"
end
# ditto
def inspect(io : IO)
to_s(io)
end
# Returns a Slice(UInt8) able to read and write bytes from a buffer.
# The slice will be long enough to hold all the bits groups in bytes despite the `UInt32` internal representation.
# It's useful for reading and writing a bit array from a byte buffer directly.
def to_slice : Slice(UInt8)
Slice.new(@bits.as(Pointer(UInt8)), (@size / 8.0).ceil.to_i)
end
private def bit_index_and_sub_index(index)
index += @size if index < 0
raise IndexError.new if index >= @size || index < 0
index.divmod(32)
end
private def malloc_size
(@size / 32.0).ceil.to_i
end
end