Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,2 @@
source "http://rubygems.org"

# Specify your gem's dependencies in bitarray.gemspec
gemspec
20 changes: 20 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
PATH
remote: .
specs:
bitarray (0.0.5)

GEM
remote: http://rubygems.org/
specs:
rake (0.9.2.2)
test-unit (2.4.5)
yard (0.7.4)

PLATFORMS
ruby

DEPENDENCIES
bitarray!
rake
test-unit
yard
45 changes: 35 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,57 @@

Basic, pure Ruby bit field. Pretty fast (for what it is) and memory efficient. Works well for Bloom filters (the reason I wrote it).

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.
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.

## Installation

gem install bitarray
```ruby
gem install bitarray
```

## Examples

To use:

require 'bitarray'
```ruby
require 'bitarray'
```

Create a bit field 1000 bits wide:
Create a bit array 1000 bits wide:

ba = BitArray.new(1000)
```ruby
ba = BitArray.new(1000)
```

Setting and reading bits:

ba[100] = 1
ba[100] .. => 1
ba[100] = 0
```ruby
ba[100] = 1
ba[100]
#=> 1

ba[100] = 0
ba[100]
#=> 0
```

More:

ba.to_s = "10101000101010101" (example)
ba.total_set .. => 10 (example - 10 bits are set to "1")
```ruby
ba = BitArray.new(20)
[1,3,5,9,11,13,15].each { |i| ba[i] = 1 }
ba.to_s
#=> "01010100010101010000"
ba.total_set
#=> 7
```

## History
- v5 (added support for flags being on by default, instead of off)
- v4 (fixed bug where setting 0 bits to 0 caused a set to 1)
- v3 (supports dynamic bitwidths for array elements.. now doing 32 bit widths default)
- v2 (now uses 1 << y, rather than 2 ** y .. it's 21.8 times faster!)
- v1 (first release)

## License

Expand Down
8 changes: 7 additions & 1 deletion bitarray.gemspec
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
# -*- encoding: utf-8 -*-
$:.push File.expand_path("../lib", __FILE__)

require 'bitarray'

Gem::Specification.new do |s|
s.name = "bitarray"
s.version = "0.0.1"
s.version = BitArray::VERSION
s.authors = ["Peter Cooper"]
s.email = ["git@peterc.org"]
s.homepage = "https://github.com/peterc/bitarray"
Expand All @@ -16,4 +18,8 @@ Gem::Specification.new do |s|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
s.require_paths = ["lib"]

s.add_development_dependency "rake"
s.add_development_dependency "yard"
s.add_development_dependency "test-unit"
end
19 changes: 10 additions & 9 deletions lib/bitarray.rb
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
class BitArray
attr_reader :size
include Enumerable

VERSION = "0.0.5"
ELEMENT_WIDTH = 32
def initialize(size)

def initialize(size, default_value = 0)
@size = size
@field = Array.new(((size - 1) / ELEMENT_WIDTH) + 1, 0)
@field.map!{|i| ~i} if (default_value == 1)
end

# Set a bit (1/0)
def []=(position, value)
if value == 1
Expand All @@ -17,22 +18,22 @@ def []=(position, value)
@field[position / ELEMENT_WIDTH] ^= 1 << (position % ELEMENT_WIDTH)
end
end

# Read a bit (1/0)
def [](position)
@field[position / ELEMENT_WIDTH] & 1 << (position % ELEMENT_WIDTH) > 0 ? 1 : 0
end

# Iterate over each bit
def each(&block)
@size.times { |position| yield self[position] }
end

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

# Returns the total number of bits that are set
# (The technique used here is about 6 times faster than using each or inject direct on the bitfield)
def total_set
Expand Down
75 changes: 46 additions & 29 deletions test/test_bitarray.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,61 +2,78 @@
require "bitarray"

class TestBitArray < Test::Unit::TestCase

def setup
@public_bf = BitArray.new(1000)
@public_ba = BitArray.new(1000)
end

def test_basic
assert_equal 0, BitArray.new(100)[0]
assert_equal 0, BitArray.new(100)[1]
end

def test_setting_and_unsetting
@public_bf[100] = 1
assert_equal 1, @public_bf[100]
@public_bf[100] = 0
assert_equal 0, @public_bf[100]
@public_ba[100] = 1
assert_equal 1, @public_ba[100]
@public_ba[100] = 0
assert_equal 0, @public_ba[100]
end

def test_random_setting_and_unsetting
100.times do
index = rand(1000)
@public_bf[index] = 1
assert_equal 1, @public_bf[index]
@public_bf[index] = 0
assert_equal 0, @public_bf[index]
@public_ba[index] = 1
assert_equal 1, @public_ba[index]
@public_ba[index] = 0
assert_equal 0, @public_ba[index]
end
end


def test_random_side_effects
ba2 = BitArray.new(@public_ba.size, 1)

on = (@public_ba.size / 2).times.collect do
index = rand(@public_ba.size)
@public_ba[index] = 1
ba2[index] = 0
index
end
assert_equal(@public_ba.to_s, @public_ba.to_s_fast)

@public_ba.size.times do |i|
assert_equal(@public_ba[i], on.include?(i) ? 1 : 0)
assert_equal(ba2[i], on.include?(i) ? 0 : 1)
end
end

def test_multiple_setting
1.upto(999) do |pos|
2.times { @public_bf[pos] = 1 }
assert_equal 1, @public_bf[pos]
2.times { @public_ba[pos] = 1 }
assert_equal 1, @public_ba[pos]
end
end

def test_multiple_unsetting
1.upto(999) do |pos|
2.times { @public_bf[pos] = 0 }
assert_equal 0, @public_bf[pos]
2.times { @public_ba[pos] = 0 }
assert_equal 0, @public_ba[pos]
end
end

def test_size
assert_equal 1000, @public_bf.size
assert_equal 1000, @public_ba.size
end

def test_to_s
bf = BitArray.new(10)
bf[1] = 1
bf[5] = 1
assert_equal "0100010000", bf.to_s
ba = BitArray.new(35)
[1, 5, 6, 7, 10, 16, 33].each{|i|ba[i] = 1}
assert_equal "01000111001000001000000000000000010", ba.to_s
end

def test_total_set
bf = BitArray.new(10)
bf[1] = 1
bf[5] = 1
assert_equal 2, bf.total_set
ba = BitArray.new(10)
ba[1] = 1
ba[5] = 1
assert_equal 2, ba.total_set
end
end
end