diff --git a/Gemfile b/Gemfile index ea578e6..1aa98e4 100644 --- a/Gemfile +++ b/Gemfile @@ -1,4 +1,2 @@ source "http://rubygems.org" - -# Specify your gem's dependencies in bitarray.gemspec gemspec diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..251ecfa --- /dev/null +++ b/Gemfile.lock @@ -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 diff --git a/README.md b/README.md index 23ede02..e733788 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/bitarray.gemspec b/bitarray.gemspec index c74e2a2..602b5d0 100644 --- a/bitarray.gemspec +++ b/bitarray.gemspec @@ -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" @@ -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 diff --git a/lib/bitarray.rb b/lib/bitarray.rb index 438a429..3220255 100644 --- a/lib/bitarray.rb +++ b/lib/bitarray.rb @@ -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 @@ -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 diff --git a/test/test_bitarray.rb b/test/test_bitarray.rb index 4e74de5..7ec27c6 100644 --- a/test/test_bitarray.rb +++ b/test/test_bitarray.rb @@ -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 \ No newline at end of file +end