]> git.donarmstrong.com Git - biopieces.git/blob - code_ruby/lib/maasha/bitarray.rb
speed optimized interval_set and interval_unset in bitarray.rb - now evil fast
[biopieces.git] / code_ruby / lib / maasha / bitarray.rb
1 # Copyright (C) 2007-2011 Martin A. Hansen.
2
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; either version 2
6 # of the License, or (at your option) any later version.
7
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 # GNU General Public License for more details.
12
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software
15 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16
17 # http://www.gnu.org/copyleft/gpl.html
18
19 # >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
20
21 # This software is part of the Biopieces framework (www.biopieces.org).
22
23 # >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
24
25 require 'narray'
26 require 'pp'
27
28 BitsInChar = 8
29
30 # Error class for all exceptions to do with BitArray.
31 class BitArrayError < StandardError; end
32
33 # Class containing methods for creating and manipulating a bit array.
34 class BitArray
35   attr_accessor :byte_array
36   attr_reader   :size
37
38   # Method to initialize a new bit array of a given size in bits.
39   def initialize(size)
40     @size        = size
41     @byte_array  = init_byte_array
42     @count_array = init_count_array
43   end
44
45   # Method that sets all bits in the bit array.
46   def fill!
47     self.byte_array.fill!(~0)
48
49     self
50   end
51
52   # Method that unsets all bits in the bit array.
53   def clear!
54     self.byte_array.fill!(0)
55
56     self
57   end
58
59   # Method to set a bit to 1 at a given position in the bit array.
60   def bit_set(pos)
61     raise BitArrayError, "Position #{pos} must be an integer."              unless pos.is_a? Fixnum
62     raise BitArrayError, "Position #{pos} outside of range: 0 ... #{@size}" unless (0 ... @size ).include? pos
63
64     @byte_array[byte_pos(pos)] = @byte_array[byte_pos(pos)] | bit_pos(pos)
65   end
66
67   # Method to set a bit to 0 at a given position in the bit array.
68   def bit_unset(pos)
69     raise BitArrayError, "Position #{pos} must be an integer."              unless pos.is_a? Fixnum
70     raise BitArrayError, "Position #{pos} outside of range: 0 ... #{@size}" unless (0 ... @size ).include? pos
71
72     mask = bit_pos(pos)
73     mask = ~mask
74
75     @byte_array[byte_pos(pos)] = @byte_array[byte_pos(pos)] & mask
76   end
77
78   # Method to check if a bit at a given position in the bit array is set.
79   def bit_set?(pos)
80     raise BitArrayError, "Position #{pos} must be an integer."              unless pos.is_a? Fixnum
81     raise BitArrayError, "Position #{pos} outside of range: 0 ... #{@size}" unless (0 ... @size ).include? pos
82
83     (@byte_array[byte_pos(pos)] & bit_pos(pos)) != 0
84   end
85
86   # Method that returns the number of bits set "on" in a bit array.
87   def bits_on
88     index = NArray.sint(*self.byte_array.shape)
89     index[] = self.byte_array
90     NArray.to_na(@count_array)[index].sum
91   end
92
93   # Method that returns the number of bits set "off" in a bit array.
94   def bits_off
95     @size - bits_on
96   end
97
98   # Method to run bitwise AND (&) on two bit arrays and return the
99   # result in the first bit array. Bits are copied if they exists in BOTH operands.
100   # 00111100 & 00001101 = 00001100
101   def &(ba)
102     raise BitArrayError, "uneven size of bit arrays: #{self.size} != #{ba.size}" if self.size != ba.size
103
104     self.byte_array = self.byte_array & ba.byte_array
105
106     self
107   end
108
109   # Method to run bitwise OR (|) on two bit arrays and return the
110   # result in the first bit array. Bits are copied if they exists in EITHER operands.
111   # 00111100 | 00001101 = 00111101
112   def |(ba)
113     raise BitArrayError, "uneven size of bit arrays: #{self.size} != #{ba.size}" if self.size != ba.size
114
115     self.byte_array = self.byte_array | ba.byte_array
116
117     self
118   end
119
120   # Method to run bitwise XOR (^) on two bit arrays and return the
121   # result in the first bit array. Bits are copied if they exists in ONE BUT NOT BOTH operands.
122   # 00111100 ^ 00001101 = 00110001
123   def ^(ba)
124     raise BitArrayError, "uneven size of bit arrays: #{self.size} != #{ba.size}" if self.size != ba.size
125
126     self.byte_array = self.byte_array ^ ba.byte_array
127
128     self
129   end
130
131   # Method to flip all bits in a bit array. All set bits become unset and visa versa.
132   def ~
133     self.byte_array = ~(self.byte_array)
134
135     self
136   end
137
138   # Method to convert a bit array to a string.
139   def to_s
140     string = ""
141
142     (0 ... @size).each do |pos|
143       if self.bit_set? pos 
144         string << "1"
145       else
146         string << "0"
147       end
148     end
149
150     string
151   end
152
153   alias :to_string :to_s
154
155   # Method to set the bits to "on" in an interval in a bit array.
156   # Returns the number of bits set.
157   def interval_set(start, stop)
158     raise BitArrayError, "interval start < 0 (#{start} < 0)"                               if start < 0
159     raise BitArrayError, "interval stop > bit array size - 1 (#{stop} > #{self.size - 1})" if stop  > self.size - 1
160     raise BitArrayError, "interval stop < interval start (#{stop} < #{start})"             if stop  < start
161
162     byte_start = start / BitsInChar
163     byte_stop  = stop  / BitsInChar
164
165     bits_start = start % BitsInChar
166     bits_stop  = stop  % BitsInChar
167
168     mask = (2 ** BitsInChar) - 1  # 11111111
169     
170     if byte_start == byte_stop
171       self.byte_array[byte_start] |= ((mask >> bits_start) & (mask << (BitsInChar - bits_stop - 1)))
172     else
173       if bits_start != 0
174         self.byte_array[byte_start] |= (mask >> bits_start)
175         byte_start += 1
176       end
177
178       self.byte_array[byte_stop] |= (mask << (BitsInChar - bits_stop - 1))
179
180       if byte_start != byte_stop
181         self.byte_array[byte_start ... byte_stop] = mask
182       end
183     end
184
185     stop - start + 1
186   end
187
188   # Method to set the bits to "off" in an interval in a bit array.
189   # Returns the number of bits unset.
190   def interval_unset(start, stop)
191     raise BitArrayError, "interval start < 0 (#{start} < 0)"                               if start < 0
192     raise BitArrayError, "interval stop > bit array size - 1 (#{stop} > #{self.size - 1})" if stop  > self.size - 1
193     raise BitArrayError, "interval stop < interval start (#{stop} < #{start})"             if stop  < start
194
195     byte_start = start / BitsInChar
196     byte_stop  = stop  / BitsInChar
197
198     bits_start = start % BitsInChar
199     bits_stop  = stop  % BitsInChar
200
201     mask = (2 ** BitsInChar) - 1  # 11111111
202
203     if byte_start == byte_stop
204       self.byte_array[byte_start] &= ~((mask >> bits_start) & (mask << (BitsInChar - bits_stop - 1)))
205     else
206       if bits_start != 0
207         self.byte_array[byte_start] &= ~(mask >> bits_start)
208         byte_start += 1
209       end
210
211       self.byte_array[byte_stop] &= ~(mask << (BitsInChar - bits_stop - 1))
212
213       if byte_start != byte_stop
214         self.byte_array[byte_start ... byte_stop] = ~mask
215       end
216     end
217
218     stop - start + 1
219   end
220
221   # Method to locate intervals of bits set to "on" in a bit array.
222   #   BitArray.each_interval -> Array
223   #   BitArray.each_interval { |start, stop| } -> Fixnum
224   def each_interval
225     intervals = []
226     start     = 0
227     stop      = 0
228
229     while start < self.size
230       if self.bit_set?(start)
231         stop = start
232
233         while stop < self.size and self.bit_set?(stop)
234           stop += 1
235         end
236
237         if block_given?
238           yield start, stop
239         else
240           intervals << [start, stop]
241         end
242       end
243
244       stop > start ? start = stop : start += 1
245     end
246
247     return intervals unless block_given?
248   end
249
250   private
251
252   # Method to initialize the byte array (string) that constitutes the bit array.
253   def init_byte_array
254     raise BitArrayError, "Size must be an integer, not #{@size}" unless @size.is_a? Fixnum
255     raise BitArrayError, "Size must be positive, not #{@size}"   unless @size > 0
256
257     NArray.byte(((@size - 1) / BitsInChar) + 1)
258   end
259
260   # Method that returns an array where the element index value is
261   # the number of bits set for that index value.
262   def init_count_array
263     count_array = []
264
265     (0 ... (2 ** BitsInChar)).each do |i|
266       count_array << bits_in_char(i)
267     end
268
269     count_array
270   end
271
272   # Method that returns the number of set bits in a char.
273   def bits_in_char(char)
274     bits = 0
275
276     (0 ... BitsInChar).each do |pos|
277       bits += 1 if ((char & bit_pos(pos)) != 0)
278     end
279
280     bits
281   end
282
283   # Method that returns the byte position in the byte array for a given bit position.
284   def byte_pos(pos)
285     pos / BitsInChar
286   end
287
288   # Method that returns the bit position in a byte.
289   def bit_pos(pos)
290     1 << (BitsInChar - 1 - (pos % BitsInChar))
291   end
292 end
293