Skip to content

Commit 0e49a18

Browse files
committed
Switched to a getbyte/setbyte approach
1 parent 628f47a commit 0e49a18

File tree

6 files changed

+79
-18
lines changed

6 files changed

+79
-18
lines changed

Gemfile.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
PATH
22
remote: .
33
specs:
4-
bitarray (0.0.5)
4+
bitarray (1.0.0)
55

66
GEM
77
remote: https://rubygems.org/

README.md

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
# BitArray: A simple bit array/bit field library in pure Ruby
1+
# BitArray: A simple bit-array/bitfield library in pure Ruby
22

3-
Basic, pure Ruby bit field. Pretty fast (for what it is) and memory efficient. Works well for Bloom filters (the reason I wrote it).
3+
Basic, pure Ruby bit field. Works well for Bloom filters (the use case for which I originally wrote it).
44

5-
Written in 2007 and not updated since then, just bringing it on to GitHub by user request. It used to be called BitField and was hosted at http://snippets.dzone.com/posts/show/4234 .. I will review the code and bring the docs up to date in due course.
5+
Originally written in 2007 and left without significant update until 2017, it has now been updated to work within a typical, modern Ruby environment while maintaining the same API.
66

77
## Installation
88

@@ -48,12 +48,18 @@ ba.total_set
4848
```
4949

5050
## History
51+
- 1.0 in 2017 (updated for modern Ruby, more efficient storage, and 10th birthday)
52+
- 0.0.1 in 2012 (original v5 released on GitHub)
5153
- v5 (added support for flags being on by default, instead of off)
5254
- v4 (fixed bug where setting 0 bits to 0 caused a set to 1)
5355
- v3 (supports dynamic bitwidths for array elements.. now doing 32 bit widths default)
5456
- v2 (now uses 1 << y, rather than 2 ** y .. it's 21.8 times faster!)
5557
- v1 (first release)
5658

59+
## Thanks
60+
61+
Thanks to Michael Slade for encouraging me to update this library on its 10th birthday and for suggesting finally using String's getbyte and setbyte methods now that we're all on 1.9+ compatible implementations.
62+
5763
## License
5864

59-
MIT licensed. Copyright 2007-2013 Peter Cooper, yada yada.
65+
MIT licensed. Copyright 2007-2013 Peter Cooper, yada yada.

lib/bitarray-array.rb

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
class BitArray
2+
attr_reader :size
3+
attr_reader :field
4+
include Enumerable
5+
6+
VERSION = "1.0.0"
7+
ELEMENT_WIDTH = 32
8+
9+
def initialize(size, field = nil)
10+
@size = size
11+
@field = field || Array.new(((size - 1) / ELEMENT_WIDTH) + 1, 0)
12+
end
13+
14+
# Set a bit (1/0)
15+
def []=(position, value)
16+
if value == 1
17+
@field[position / ELEMENT_WIDTH] |= 1 << (position % ELEMENT_WIDTH)
18+
elsif (@field[position / ELEMENT_WIDTH]) & (1 << (position % ELEMENT_WIDTH)) != 0
19+
@field[position / ELEMENT_WIDTH] ^= 1 << (position % ELEMENT_WIDTH)
20+
end
21+
end
22+
23+
# Read a bit (1/0)
24+
def [](position)
25+
@field[position / ELEMENT_WIDTH] & 1 << (position % ELEMENT_WIDTH) > 0 ? 1 : 0
26+
end
27+
28+
# Iterate over each bit
29+
def each(&block)
30+
@size.times { |position| yield self[position] }
31+
end
32+
33+
# Returns the field as a string like "0101010100111100," etc.
34+
def to_s
35+
@field.collect{|ea| ("%0#{ELEMENT_WIDTH}b" % ea).reverse}.join[0..@size-1]
36+
end
37+
38+
# Returns the total number of bits that are set
39+
# (The technique used here is about 6 times faster than using each or inject direct on the bitfield)
40+
def total_set
41+
@field.inject(0) { |a, byte| a += byte & 1 and byte >>= 1 until byte == 0; a }
42+
end
43+
end

lib/bitarray.rb

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,25 @@ class BitArray
33
attr_reader :field
44
include Enumerable
55

6-
VERSION = "0.0.5"
7-
ELEMENT_WIDTH = 32
6+
VERSION = "1.0.0"
87

98
def initialize(size, field = nil)
109
@size = size
11-
@field = field || Array.new(((size - 1) / ELEMENT_WIDTH) + 1, 0)
10+
@field = "\0" * (size / 8 + 1)
1211
end
1312

1413
# Set a bit (1/0)
1514
def []=(position, value)
1615
if value == 1
17-
@field[position / ELEMENT_WIDTH] |= 1 << (position % ELEMENT_WIDTH)
18-
elsif (@field[position / ELEMENT_WIDTH]) & (1 << (position % ELEMENT_WIDTH)) != 0
19-
@field[position / ELEMENT_WIDTH] ^= 1 << (position % ELEMENT_WIDTH)
16+
@field.setbyte(position >> 3, @field.getbyte(position >> 3) | (1 << (position % 8)))
17+
else
18+
@field.setbyte(position >> 3, @field.getbyte(position >> 3) ^ (1 << (position % 8)))
2019
end
2120
end
2221

2322
# Read a bit (1/0)
2423
def [](position)
25-
@field[position / ELEMENT_WIDTH] & 1 << (position % ELEMENT_WIDTH) > 0 ? 1 : 0
24+
(@field.getbyte(position >> 3) & (1 << (position % 8))) > 0 ? 1 : 0
2625
end
2726

2827
# Iterate over each bit
@@ -32,12 +31,12 @@ def each(&block)
3231

3332
# Returns the field as a string like "0101010100111100," etc.
3433
def to_s
35-
@field.collect{|ea| ("%032b" % ea).reverse}.join[0..@size-1]
34+
@field.bytes.collect{|ea| ("%08b" % ea).reverse}.join[0, @size]
3635
end
3736

3837
# Returns the total number of bits that are set
3938
# (The technique used here is about 6 times faster than using each or inject direct on the bitfield)
4039
def total_set
41-
@field.inject(0) { |a, byte| a += byte & 1 and byte >>= 1 until byte == 0; a }
40+
@field.bytes.inject(0) { |a, byte| a += byte & 1 and byte >>= 1 until byte == 0; a }
4241
end
43-
end
42+
end

memory-test.rb

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
require_relative 'lib/bitarray'
2+
require 'memory_profiler'
3+
4+
report = MemoryProfiler.report do
5+
ba = BitArray.new(1000000)
6+
7+
1000000.times do |i|
8+
ba[rand(100000)] = 1
9+
ba[rand(100000)] = 0
10+
end
11+
end
12+
13+
report.pretty_print

test/test_bitarray.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,13 @@
22
require "bitarray"
33

44
class TestBitArray < Minitest::Test
5-
65
def setup
76
@public_ba = BitArray.new(1000)
87
end
98

109
def test_basic
1110
assert_equal 0, BitArray.new(100)[0]
12-
assert_equal 0, BitArray.new(100)[1]
11+
assert_equal 0, BitArray.new(100)[99]
1312
end
1413

1514
def test_setting_and_unsetting
@@ -57,6 +56,7 @@ def test_total_set
5756
ba = BitArray.new(10)
5857
ba[1] = 1
5958
ba[5] = 1
60-
assert_equal 2, ba.total_set
59+
ba[9] = 1
60+
assert_equal 3, ba.total_set
6161
end
6262
end

0 commit comments

Comments
 (0)