bitindex 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/README.md ADDED
@@ -0,0 +1,39 @@
1
+ # Bitindex
2
+
3
+ Bitindex is a simple gem for writing binary files where the position of a 1 in the bits of the file indicates a true value and 0 indicates a false. The default is to read from left to right so that a file that consists of `1000 1000` could be read to return true for the value 0 and the value 4 and false for all other integer values.
4
+
5
+ ```ruby
6
+ require 'bitindex'
7
+
8
+ file = 'example.bits'
9
+ size_in_bytes = 1024
10
+ w = Bitindex::Writer.new file, size_in_bytes
11
+ w.write [1,2,3,8000]
12
+
13
+ r = Bitindex::Reader.new file
14
+ r.is_set? 8000 # true
15
+ r.is_set? 8001 # false
16
+ r.is_set? 2**32 # false
17
+ r.all_set(0..10) # [1,2,3]
18
+
19
+ ```
20
+
21
+
22
+ # Install
23
+
24
+ ```ruby
25
+ require 'bitindex'
26
+ ```
27
+
28
+ # Specifics
29
+
30
+ A project I was working was given a file that mapped the 32bit address space into a 530MB file where bits set to 1 indicated integer values that were set. I decided to expand the library to handle both the reading and writing of these files. Creating files of that size on my desktop (few year old MacBook Pro) takes about X minutes. But reading from the file is fairly quick.
31
+
32
+ Future enhancements:
33
+ * compressing and decompressing the file
34
+ * <del>optimizing the Writer so that it faster for index files with long sequences of 1s or 0s</del> A `block_size` option has been added to enable long blocks of zeros. On my laptop, the creation of a 530M index with index values went from 10 minutes to about a minute using `{block_size: 1024*1024}`.
35
+ * allowing the Writer to set and unset individual bits
36
+
37
+ # Limitations
38
+
39
+ This library ignores the potential difference of hardware, bit ordering and all that stuff and relys on the ruby implementation to handle those details. It is suitable for creating and using files under a common platform and operating system.
data/Rakefile ADDED
@@ -0,0 +1,5 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+ RSpec::Core::RakeTask.new('spec')
4
+
5
+ task :default => 'spec'
@@ -0,0 +1,39 @@
1
+ module Bitindex
2
+ module Common
3
+
4
+ SIGNED_16BIT = 2**15 / 8
5
+ UNSIGNED_16BIT = 2**16 / 8
6
+
7
+ SIGNED_32BIT = 2**31 / 8
8
+ UNSIGNED_32BIT = 2**32 / 8
9
+
10
+ def bit_set? byte, value, ltor = true
11
+ mask = build_mask value, ltor
12
+ (byte & mask) != 0
13
+ end
14
+
15
+ def set_bit byte, value, ltor = true
16
+ mask = build_mask value, ltor
17
+ (byte | mask)
18
+ end
19
+
20
+ def unset_bit byte, value, ltor = true
21
+ mask = build_mask value, ltor
22
+ mask ^= 0xff
23
+ (byte & mask)
24
+ end
25
+
26
+ def byte_pos value
27
+ (value >> 3)
28
+ end
29
+
30
+ def build_mask value, ltor = true
31
+ shift = value & 0x07
32
+ if ltor
33
+ (0x80 >> shift)
34
+ else
35
+ (0x01 << shift)
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,47 @@
1
+ module Bitindex
2
+ class Reader
3
+ include Bitindex::Common
4
+
5
+ attr_reader :filepath
6
+ attr_accessor :ltor
7
+
8
+ def initialize filepath, opts = {}
9
+
10
+ raise "#{ filepath } is not readable" unless filepath and File.readable?(filepath)
11
+ @filepath = filepath
12
+
13
+ @ltor = !(opts[:ltor] == false)
14
+ end
15
+
16
+ def is_set? value
17
+ result = false
18
+ File.open(self.filepath, 'rb') do |io|
19
+ idx = byte_pos value
20
+ if idx < io.size
21
+ io.seek idx, IO::SEEK_SET
22
+ result = bit_set? io.getbyte, value, self.ltor
23
+ end
24
+ end
25
+ result
26
+ end
27
+
28
+ def all_set array
29
+ arr = []
30
+ File.open(self.filepath, 'rb') do |io|
31
+ cur_idx = nil
32
+ cur_byte = nil
33
+ array.each do |n|
34
+ idx = byte_pos n
35
+ if idx < io.size
36
+ if cur_idx.nil? or cur_idx < idx
37
+ io.seek idx, IO::SEEK_SET
38
+ cur_byte = io.getbyte
39
+ end
40
+ arr << n if bit_set? cur_byte, n, self.ltor
41
+ end
42
+ end
43
+ end
44
+ arr
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,3 @@
1
+ module Bitindex
2
+ VERSION = "0.0.2"
3
+ end
@@ -0,0 +1,63 @@
1
+ module Bitindex
2
+ class Writer
3
+ include Bitindex::Common
4
+
5
+ attr_reader :filepath, :size
6
+ attr_reader :ltor
7
+ attr_reader :block_size
8
+
9
+ def initialize filepath, size, opts = {}
10
+ raise "#{ size } is invalid size" unless size and size.to_i > 0
11
+ @size = size
12
+
13
+ raise "#{ filepath } is invalid file" unless filepath
14
+ @filepath = filepath
15
+
16
+ @ltor = !(opts[:ltor] == false)
17
+ @block_size = (opts[:block_size] and opts[:block_size].to_i > 0) ? opts[:block_size].to_i : 1024
18
+ end
19
+
20
+ def write sorted
21
+ File.open(self.filepath, 'wb') do |io|
22
+ last_num = nil
23
+ cur_byte = 0
24
+ cur_idx = 0
25
+ sorted.each do |n|
26
+ raise "values must be sorted" unless last_num.nil? or n >= last_num
27
+
28
+ idx = byte_pos n
29
+ if idx < self.size
30
+ if idx > cur_idx
31
+ io.putc cur_byte
32
+ pad_to_pos io, cur_idx, idx - 1
33
+ cur_idx = idx
34
+ cur_byte = 0
35
+ end
36
+
37
+ cur_byte = set_bit cur_byte, n, self.ltor
38
+ end
39
+ end
40
+
41
+ io.putc cur_byte
42
+ pad_to_pos io, cur_idx, self.size - 1 if cur_idx < self.size - 1
43
+ end
44
+ end
45
+
46
+ protected
47
+
48
+ def pad_to_pos io, start, last
49
+ while start < last
50
+ diff = last - start
51
+ if diff > self.block_size
52
+ a = Array.new(self.block_size, 0)
53
+ io.write(a.pack('C' * self.block_size))
54
+ start += self.block_size
55
+ else
56
+ a = Array.new(diff, 0)
57
+ io.write(a.pack('C' * diff))
58
+ start = last
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
data/lib/bitindex.rb ADDED
@@ -0,0 +1,7 @@
1
+ require 'bitindex/version'
2
+ require 'bitindex/common'
3
+ require 'bitindex/reader'
4
+ require 'bitindex/writer'
5
+
6
+ module Bitindex
7
+ end
@@ -0,0 +1,73 @@
1
+ require 'spec_helper'
2
+
3
+ class BitIndexCommonTest; include Bitindex::Common; end
4
+
5
+ describe Bitindex::Common do
6
+
7
+ before(:each) { @test = BitIndexCommonTest.new }
8
+
9
+ describe '.set_bit' do
10
+ it 'sets bits left to right' do
11
+ @test.set_bit(0x00, 7).should == 0x01 # 0000 0001
12
+ @test.set_bit(0x00, 0).should == 0x80 # 1000 0000
13
+ end
14
+
15
+ it 'sets bits right to left' do
16
+ @test.set_bit(0x00, 7, false).should == 0x80 # 1000 0000
17
+ @test.set_bit(0x00, 0, false).should == 0x01 # 0000 0001
18
+ end
19
+
20
+ it 'does not modify other bits' do
21
+ byte = 0x00
22
+ (0..7).each {|n| byte = @test.set_bit(byte, n) }
23
+ byte.should == 0xff
24
+ end
25
+
26
+ it 'does not modify a previously set bit' do
27
+ @test.set_bit(0x88, 0).should == 0x88
28
+ @test.set_bit(0x11, 0, false).should == 0x11
29
+ end
30
+ end
31
+
32
+ describe '.unset_bit' do
33
+ it 'unsets bits left to right' do
34
+ @test.unset_bit(0xff, 7).should == 0xfe # 1111 1110
35
+ @test.unset_bit(0xff, 0).should == 0x7f # 0111 1111
36
+ end
37
+
38
+ it 'unsets bits right to left' do
39
+ @test.unset_bit(0xff, 7, false).should == 0x7f # 0111 1111
40
+ @test.unset_bit(0xff, 0, false).should == 0xfe # 1111 1110
41
+ end
42
+
43
+ it 'does not modify other bits' do
44
+ byte = 0xff
45
+ (0..7).each {|n| byte = @test.unset_bit(byte, n) }
46
+ byte.should == 0x00
47
+ end
48
+
49
+ it 'does not modify a previously unset bit' do
50
+ @test.unset_bit(0x77, 0).should == 0x77 # 0111 0111
51
+ @test.unset_bit(0xee, 0, false).should == 0xee # 1110 1110
52
+ end
53
+ end
54
+
55
+ describe '.bit_set?' do
56
+ it 'should determine if a bit within a byte is set' do
57
+ (0..15).each {|n| @test.bit_set?(0x00, n).should be_false }
58
+ (0..15).each {|n| @test.bit_set?(0xff, n).should be_true }
59
+ end
60
+
61
+ it 'finds set bit left to right' do
62
+ byte = 0xf0 # 11110000
63
+ (0..3).each {|n| @test.bit_set?(byte, n).should be_true }
64
+ (4..7).each {|n| @test.bit_set?(byte, n).should be_false }
65
+ end
66
+
67
+ it 'finds set bit right to left' do
68
+ byte = 0xf0 # 11110000
69
+ (0..3).each {|n| @test.bit_set?(byte, n, false).should be_false }
70
+ (4..7).each {|n| @test.bit_set?(byte, n, false).should be_true }
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,121 @@
1
+ require 'spec_helper'
2
+
3
+ describe Bitindex::Reader do
4
+
5
+ describe '.initialize' do
6
+ it 'requires a valid file path' do
7
+ expect { Bitindex::Reader.new }.to raise_error
8
+ expect { Bitindex::Reader.new 'not_there.bit' }.to raise_error
9
+
10
+ filepath = 'is_there.bit'
11
+ FileUtils.touch filepath
12
+ r = Bitindex::Reader.new filepath
13
+ r.filepath.should == filepath
14
+ FileUtils.rm filepath
15
+ end
16
+
17
+ it 'can set left to right as an option' do
18
+
19
+ filepath = 'is_there.bit'
20
+ FileUtils.touch filepath
21
+
22
+ r = Bitindex::Reader.new filepath
23
+ r.ltor.should be_true
24
+
25
+ r = Bitindex::Reader.new filepath, { ltor: false }
26
+ r.ltor.should be_false
27
+
28
+ FileUtils.rm filepath
29
+ end
30
+ end
31
+
32
+ describe '.is_set?' do
33
+ it 'can read values in a single byte file' do
34
+
35
+ filepath = 'reader.bit'
36
+ File.open(filepath, 'wb') do |io|
37
+ io.putc 0x88 # 1000 1000
38
+ end
39
+
40
+ r = Bitindex::Reader.new filepath
41
+ r.is_set?(0).should be_true
42
+ r.is_set?(1).should be_false
43
+ r.is_set?(3).should be_false
44
+ r.is_set?(4).should be_true
45
+ r.is_set?(5).should be_false
46
+ r.is_set?(7).should be_false
47
+
48
+
49
+ r = Bitindex::Reader.new filepath, { ltor: false }
50
+ r.is_set?(0).should be_false
51
+ r.is_set?(1).should be_false
52
+ r.is_set?(3).should be_true
53
+ r.is_set?(4).should be_false
54
+ r.is_set?(5).should be_false
55
+ r.is_set?(7).should be_true
56
+
57
+ FileUtils.rm filepath
58
+ end
59
+
60
+ it 'can read values in a multi-byte file' do
61
+
62
+ filepath = 'reader.bit'
63
+
64
+ # 1111 1111 0000 0000 1000 1000
65
+ File.open(filepath, 'wb') do |io|
66
+ io.putc 0xff
67
+ io.putc 0x00
68
+ io.putc 0x88
69
+ end
70
+
71
+ r = Bitindex::Reader.new filepath
72
+ r.is_set?(0).should be_true
73
+ r.is_set?(8).should be_false
74
+ r.is_set?(16).should be_true
75
+ r.is_set?(24).should be_false
76
+
77
+ r = Bitindex::Reader.new filepath, { ltor: false }
78
+ r.is_set?(0).should be_true
79
+ r.is_set?(8).should be_false
80
+ r.is_set?(16).should be_false
81
+ r.is_set?(24).should be_false
82
+
83
+ FileUtils.rm filepath
84
+ end
85
+ end
86
+
87
+ describe '.all_set' do
88
+ it 'can read values in a single byte file' do
89
+ filepath = 'reader.bit'
90
+ File.open(filepath, 'wb') do |io|
91
+ io.putc 0x77 # 0111 0111
92
+ end
93
+
94
+ r = Bitindex::Reader.new filepath
95
+ r.all_set(0..10).should == [1,2,3,5,6,7]
96
+
97
+ r = Bitindex::Reader.new filepath, ltor: false
98
+ r.all_set(0..10).should == [0,1,2,4,5,6]
99
+
100
+ FileUtils.rm filepath
101
+ end
102
+
103
+ it 'can read values in a multi-byte file' do
104
+ filepath = 'reader.bit'
105
+ # 1111 1111 0000 0000 1000 1000
106
+ File.open(filepath, 'wb') do |io|
107
+ io.putc 0xff
108
+ io.putc 0x00
109
+ io.putc 0x88
110
+ end
111
+
112
+ r = Bitindex::Reader.new filepath
113
+ r.all_set(0..100).should == [0,1,2,3,4,5,6,7,16,20]
114
+
115
+ r = Bitindex::Reader.new filepath, ltor: false
116
+ r.all_set(0..100).should == [0,1,2,3,4,5,6,7,19,23]
117
+
118
+ FileUtils.rm filepath
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,7 @@
1
+ require 'rspec'
2
+ require 'bitindex'
3
+
4
+ RSpec.configure do |config|
5
+ config.color_enabled = true
6
+ config.formatter = 'documentation'
7
+ end
@@ -0,0 +1,70 @@
1
+ require 'spec_helper'
2
+
3
+ describe Bitindex::Writer do
4
+
5
+ describe '.initialize' do
6
+ it 'requires a filepath and size' do
7
+ expect { Bitindex::Writer.new }.to raise_error
8
+ expect { Bitindex::Writer.new 'file.bit' }.to raise_error
9
+ expect { Bitindex::Writer.new 10 }.to raise_error
10
+ w = Bitindex::Writer.new 'file.bit', 10
11
+ w.filepath.should == 'file.bit'
12
+ w.size.should == 10
13
+ end
14
+
15
+ it 'can set "left to right" as an option' do
16
+ w = Bitindex::Writer.new 'file.bit', 10
17
+ w.ltor.should be_true
18
+
19
+ w = Bitindex::Writer.new 'file.bit', 10, { ltor: false }
20
+ w.ltor.should be_false
21
+ end
22
+ end
23
+
24
+ describe '.write' do
25
+ it 'writes a single byte file' do
26
+ filepath = 'file.bit'
27
+ w = Bitindex::Writer.new filepath, 1
28
+ w.write [0, 1, 5, 7]
29
+
30
+ File.open(filepath, 'rb') do |io|
31
+ io.size.should == 1
32
+ io.getbyte.should == 0xc5 # 1100 0101
33
+ end
34
+
35
+ FileUtils.rm filepath
36
+ end
37
+
38
+ it 'writes a multi-byte file' do
39
+ filepath = 'file.bit'
40
+ w = Bitindex::Writer.new filepath, 3
41
+ w.write [0,1,2,3,4,5,6,7,16]
42
+
43
+ File.open(filepath, 'rb') do |io|
44
+ io.size.should == 3
45
+ io.getbyte.should == 0xff
46
+ io.getbyte.should == 0x00
47
+ io.getbyte.should == 0x80
48
+ end
49
+
50
+ FileUtils.rm filepath
51
+ end
52
+
53
+ it 'writes a sparse multi-byte file' do
54
+ filepath = 'file.bit'
55
+ filesize = 1024
56
+ w = Bitindex::Writer.new filepath, filesize
57
+ w.write [1,2,3,8000]
58
+
59
+ File.open(filepath, 'rb') do |io|
60
+ io.size.should == filesize
61
+ io.seek(999, IO::SEEK_SET)
62
+ io.getbyte.should == 0x00
63
+ io.getbyte.should == 0x80
64
+ io.getbyte.should == 0x00
65
+ end
66
+
67
+ FileUtils.rm filepath
68
+ end
69
+ end
70
+ end
metadata ADDED
@@ -0,0 +1,88 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bitindex
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 0
8
+ - 2
9
+ version: 0.0.2
10
+ platform: ruby
11
+ authors:
12
+ - Galen Palmer
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2012-07-08 00:00:00 -04:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: rspec
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ~>
26
+ - !ruby/object:Gem::Version
27
+ segments:
28
+ - 2
29
+ - 10
30
+ version: "2.10"
31
+ type: :development
32
+ version_requirements: *id001
33
+ description: This library contains a Writer and Reader interface to create files where the position of a bit within the file indicates a true or false value.
34
+ email:
35
+ - palmergs@gmail.com
36
+ executables: []
37
+
38
+ extensions: []
39
+
40
+ extra_rdoc_files: []
41
+
42
+ files:
43
+ - Rakefile
44
+ - lib/bitindex/common.rb
45
+ - lib/bitindex/reader.rb
46
+ - lib/bitindex/version.rb
47
+ - lib/bitindex/writer.rb
48
+ - lib/bitindex.rb
49
+ - spec/common_spec.rb
50
+ - spec/reader_spec.rb
51
+ - spec/spec_helper.rb
52
+ - spec/writer_spec.rb
53
+ - README.md
54
+ has_rdoc: true
55
+ homepage: http://malachitedesigns.com
56
+ licenses: []
57
+
58
+ post_install_message:
59
+ rdoc_options: []
60
+
61
+ require_paths:
62
+ - lib
63
+ required_ruby_version: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ segments:
68
+ - 0
69
+ version: "0"
70
+ required_rubygems_version: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ segments:
75
+ - 0
76
+ version: "0"
77
+ requirements: []
78
+
79
+ rubyforge_project: bitindex
80
+ rubygems_version: 1.3.6
81
+ signing_key:
82
+ specification_version: 3
83
+ summary: Build a file where, given an integer value, the bit at that position indicates true or false.
84
+ test_files:
85
+ - spec/common_spec.rb
86
+ - spec/reader_spec.rb
87
+ - spec/spec_helper.rb
88
+ - spec/writer_spec.rb