bit_magic 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.coco.yml +4 -0
- data/.gitignore +10 -0
- data/.travis.yml +7 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +24 -0
- data/LICENSE.txt +21 -0
- data/README.md +290 -0
- data/Rakefile +39 -0
- data/TODO.md +25 -0
- data/bin/console +14 -0
- data/bin/setup +9 -0
- data/bit_magic.gemspec +30 -0
- data/lib/bit_magic.rb +9 -0
- data/lib/bit_magic/adapters/active_record_adapter.rb +250 -0
- data/lib/bit_magic/adapters/base.rb +61 -0
- data/lib/bit_magic/adapters/magician.rb +226 -0
- data/lib/bit_magic/adapters/mongoid_adapter.rb +233 -0
- data/lib/bit_magic/bit_field.rb +103 -0
- data/lib/bit_magic/bits.rb +285 -0
- data/lib/bit_magic/bits_generator.rb +399 -0
- data/lib/bit_magic/error.rb +7 -0
- data/lib/bit_magic/railtie.rb +28 -0
- data/lib/bit_magic/version.rb +7 -0
- metadata +129 -0
@@ -0,0 +1,103 @@
|
|
1
|
+
require_relative "./error"
|
2
|
+
|
3
|
+
module BitMagic
|
4
|
+
|
5
|
+
# Helper class to encapsulate bit field values and read/write operations
|
6
|
+
# Note that indices are based off ruby bit reading, so least-significant bits
|
7
|
+
# are to the right and negative numbers are handled as two's complement.
|
8
|
+
#
|
9
|
+
# @attr [Integer] value the current integer value that contains the bit fields
|
10
|
+
class BitField
|
11
|
+
attr_reader :value
|
12
|
+
|
13
|
+
# Initialize the BitField with an optional value. Default is 0
|
14
|
+
#
|
15
|
+
# @param [Integer] value the integer that contains the bit fields
|
16
|
+
def initialize(value = 0)
|
17
|
+
if value.is_a?(Integer)
|
18
|
+
@value = value
|
19
|
+
else
|
20
|
+
raise InputError.new("BitField#new expects an integer value, #{value.inspect} is not an integer")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Read the specified bit indices into a hash with bit index as key
|
25
|
+
#
|
26
|
+
# @param [Integer] bits one or more bit indices to read.
|
27
|
+
#
|
28
|
+
# @example Read a list of bits into a hash
|
29
|
+
# bit_field = BitField.new(5)
|
30
|
+
# bit_field.read_bits(0, 1, 2)
|
31
|
+
# #=> {0=>1, 1=>0, 2=>1}
|
32
|
+
# # because 5 is 101 in binary
|
33
|
+
#
|
34
|
+
# @return [Hash] a hash with the bit index as key and bit (1 or 0) as value
|
35
|
+
def read_bits(*args)
|
36
|
+
{}.tap do |m|
|
37
|
+
args.each { |bit| m[bit] = @value[bit] }
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Read the specified bit indices as a group, in the order given
|
42
|
+
#
|
43
|
+
# @param [Integer] bits one or more bit indices to read. Order matters!
|
44
|
+
#
|
45
|
+
# @example Read bits or a list of bits into an integer
|
46
|
+
# bit_field = BitField.new(101) # 1100101 in binary, lsb on the right
|
47
|
+
# bit_field.read_field(0, 1, 2) #=> 5 # or 101
|
48
|
+
# bit_field.read_field(0) #= 1
|
49
|
+
# bit_field.read_field( (2..6).to_a ) #=> 25 # or 11001
|
50
|
+
#
|
51
|
+
# @return [Integer] the value of the bits read together into an integer
|
52
|
+
def read_field(*args)
|
53
|
+
m = 0
|
54
|
+
args.flatten.each_with_index do |bit, i|
|
55
|
+
if bit.is_a?(Integer)
|
56
|
+
m |= ((@value[bit] || 0) << i)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
m
|
60
|
+
end
|
61
|
+
|
62
|
+
alias :[] :read_field
|
63
|
+
|
64
|
+
# Write to the specified bits, changing the internal @value to the new value
|
65
|
+
#
|
66
|
+
# @param [Hash] bit_values a hash with the key being a bit index and value
|
67
|
+
# being the value (must be 1, 0, true or false)
|
68
|
+
#
|
69
|
+
# @example Write new bit withs with their corresponding values
|
70
|
+
# bit_field = BitField.new
|
71
|
+
# bit_field.write_bits(0 => true) #=> 1
|
72
|
+
# bit_field.write_bits(1 => true, 4 => true) #=> 19 # 10011
|
73
|
+
# bit_field.write_bits(0 => false, 4 => false) #=> 2 # 10
|
74
|
+
#
|
75
|
+
# @return [Integer] the value after writing the new bits their new values
|
76
|
+
def write_bits(bit_values = {})
|
77
|
+
bit_values.each_pair do |index, val|
|
78
|
+
|
79
|
+
if !index.is_a?(Integer)
|
80
|
+
raise InputError.new("BitField#write can only access bits by their index, #{index.inspect} is not a valid index")
|
81
|
+
end
|
82
|
+
|
83
|
+
if index < 0
|
84
|
+
raise InputError.new("BitField#write can not write to negative indices")
|
85
|
+
end
|
86
|
+
|
87
|
+
if !(val === true) and !(val === false) and !(val === 1) and !(val === 0)
|
88
|
+
raise InputError.new("BitField#write must have a boolean value, #{val.inspect} is not a boolean")
|
89
|
+
end
|
90
|
+
|
91
|
+
if val === true or val === 1
|
92
|
+
@value |= (1 << index)
|
93
|
+
else
|
94
|
+
@value &= ~(1 << index)
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
|
99
|
+
@value
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,285 @@
|
|
1
|
+
require_relative './bit_field'
|
2
|
+
|
3
|
+
module BitMagic
|
4
|
+
# This is a wrapper class for objects that want bitfield functionality.
|
5
|
+
# It implements bit field read, write and attribute field read and updating.
|
6
|
+
#
|
7
|
+
# This is usually used alongside Magician (an adapter helper class).
|
8
|
+
#
|
9
|
+
# If you're using this class directly, you can subclass it and set DEFAULT_OPTIONS
|
10
|
+
# on your subclass to change default options.
|
11
|
+
#
|
12
|
+
# @example Subclass this class to change defaults
|
13
|
+
# # (do not set attribute_name to 'object_id' in real code, it's an example)
|
14
|
+
# class MyBits < BitMagic::Bits
|
15
|
+
# DEFAULT_OPTIONS = BitMagic::Bits::DEFAULT_OPTIONS.merge({default: 99, :attribute_name => 'object_id'})
|
16
|
+
# end
|
17
|
+
# # and now, if you initialize a new MyBits...
|
18
|
+
# MyBits.new(Object.new).options
|
19
|
+
# # will have default: 99 (instead of 0), and attribute_name: 'object_id'
|
20
|
+
#
|
21
|
+
#
|
22
|
+
# @attr_reader instance the instance we're doing operations for
|
23
|
+
# @attr_reader [Hash] field_list a hash of field name => field bits key-pairs
|
24
|
+
# @attr_reader [Hash] options options for this instance
|
25
|
+
class Bits
|
26
|
+
# This casts a given input value into a boolean.
|
27
|
+
BOOLEAN_CASTER = lambda {|i| !(i == false or i == 0) }
|
28
|
+
|
29
|
+
# Default options
|
30
|
+
#
|
31
|
+
# The bool_caster is expected to be overwritten depending on your use-case.
|
32
|
+
# eg, form fields can send '0' or 'f' to mean false.
|
33
|
+
DEFAULT_OPTIONS = {
|
34
|
+
:attribute_name => 'flags',
|
35
|
+
:default => 0,
|
36
|
+
:updater => Proc.new {|bits, new_value| bits.instance.send(:"#{bits.attribute_name}=", new_value) },
|
37
|
+
:bool_caster => BOOLEAN_CASTER
|
38
|
+
}.freeze
|
39
|
+
|
40
|
+
attr_reader :instance, :field_list, :options
|
41
|
+
|
42
|
+
# This class wraps around any arbitrary objects (instance) that respond to
|
43
|
+
# certain methods (a getter and setter for the flag field).
|
44
|
+
#
|
45
|
+
# @param [Object] instance some arbitrary object with bit_magic interest
|
46
|
+
# @param [BitMagic::Adapters::Magician, Hash] magician_or_field_list either
|
47
|
+
# an instance of Magician (usually from an adapter) or a Hash with
|
48
|
+
# field name => bit/field bits array as key-pair.
|
49
|
+
# @param [Hash] options additional options to override defaults
|
50
|
+
# @option options [String] :attribute_name the name for the attribute, will
|
51
|
+
# be used as the getter and setter (by appending '=') on the instance object
|
52
|
+
# default: 'flags'
|
53
|
+
# @option options [Integer] :default the default value. default: 0
|
54
|
+
# @option options [Proc] :updater a callable (Proc/lambda/Method) used to
|
55
|
+
# update the bit field attribute after it has been changed.
|
56
|
+
# default: calls "#{attribute_name}=(newValue)" on instance
|
57
|
+
# @option options [Proc] :bool_caster a callable (Proc/lambda/Method) used to
|
58
|
+
# cast some input value into a boolean (used with #write)
|
59
|
+
# default: cast false or 0 (integer) as false, everything else as true
|
60
|
+
#
|
61
|
+
# @example Initialize a Bits object
|
62
|
+
# Example = Struct.new('Example', :flags)
|
63
|
+
# # above, Example is a class with the 'flags' instance method
|
64
|
+
# # below, we initialize an instance with flags set to 0
|
65
|
+
# bits = BitMagic::Bits.new(Example.new(0), {:is_odd => 0, :amount => [1, 2, 3], :is_cool => 4})
|
66
|
+
#
|
67
|
+
# @return [BitMagic::Bits] a Bits object
|
68
|
+
def initialize(instance, magician_or_field_list = {}, options = {})
|
69
|
+
if defined?(BitMagic::Adapters::Magician) and magician_or_field_list.is_a?(BitMagic::Adapters::Magician)
|
70
|
+
@magician = magician_or_field_list
|
71
|
+
@field_list = @magician.field_list
|
72
|
+
@options = @magician.action_options.merge(options)
|
73
|
+
else
|
74
|
+
@field_list = magician_or_field_list
|
75
|
+
@options = self.class::DEFAULT_OPTIONS.merge(options)
|
76
|
+
end
|
77
|
+
@instance = instance
|
78
|
+
end
|
79
|
+
|
80
|
+
# Get the attribute name option (a method on the instance that returns the
|
81
|
+
# current bit field value).
|
82
|
+
#
|
83
|
+
# In the future, attribute name may be a Proc. This class could also possibly
|
84
|
+
# be subclassed and this method overwritten if advanced lookup is necessary.
|
85
|
+
#
|
86
|
+
# @return [String] name of the method we will use to get the bit field value
|
87
|
+
def attribute_name
|
88
|
+
@options[:attribute_name]
|
89
|
+
end
|
90
|
+
|
91
|
+
# Get the current value from the instance. It should return an integer if the
|
92
|
+
# value exists, otherwise anything falsy will be set to the default value
|
93
|
+
# given during initialization.
|
94
|
+
#
|
95
|
+
# @return [Integer] the current value for the bit field
|
96
|
+
def value
|
97
|
+
value = @instance.send(attribute_name)
|
98
|
+
value ||= @options[:default]
|
99
|
+
value
|
100
|
+
end
|
101
|
+
|
102
|
+
# Update the bit field value on the instance with a new value using the updater
|
103
|
+
# Proc given during initialization.
|
104
|
+
#
|
105
|
+
# @param [Integer] new_value the new value for the bit field
|
106
|
+
#
|
107
|
+
# @return returns the value returned by the updater, usually is the new_value
|
108
|
+
def update(new_value)
|
109
|
+
if @options[:updater].respond_to?(:call)
|
110
|
+
@options[:updater].call(self, new_value)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# Check whether all the given field name or bits are enabled (true or 1)
|
115
|
+
# On fields with more than one bit, will return true if any of the bits are
|
116
|
+
# enabled (value > 0)
|
117
|
+
#
|
118
|
+
# @param [Symbol, Integer] fields one or more field names or bit indices
|
119
|
+
#
|
120
|
+
# @example Check fields to see if they are enabled
|
121
|
+
# # The struct is just an example, normally you would define a new class
|
122
|
+
# Example = Struct.new('Example', :flags)
|
123
|
+
# exo = Example.new(0)
|
124
|
+
# bits = Bits.new(exo, {:is_odd => 0, :amount => [1, 2, 3], :is_cool => 4})
|
125
|
+
# # we initialized flags to 0, so nothing is enabled
|
126
|
+
# bits.enabled?(:is_odd) #=> false
|
127
|
+
# bits.enabled?(:amount, :is_cool) #=> false
|
128
|
+
# bits.enabled?(10, 5, :is_odd) #=> false
|
129
|
+
#
|
130
|
+
# # We now change flags on our instance object
|
131
|
+
# exo.flags = 5 # is_odd = 1, amount = 2, is_cool = 0
|
132
|
+
# bits.enabled?(:is_odd) #=> true
|
133
|
+
# bits.enabled?(:amount, :is_cool) #=> false
|
134
|
+
# bits.enabled?(:amount, :is_odd) #=> true
|
135
|
+
# bits.enabled?(:is_cool) #=> false
|
136
|
+
#
|
137
|
+
#
|
138
|
+
# @return [Boolean] true if ALL the given field bits are enabled
|
139
|
+
def enabled?(*fields)
|
140
|
+
memo = true
|
141
|
+
field = self.field
|
142
|
+
|
143
|
+
fields.flatten.each do |name|
|
144
|
+
break unless memo
|
145
|
+
memo &&= (read(name, field) >= 1)
|
146
|
+
end
|
147
|
+
|
148
|
+
memo
|
149
|
+
end
|
150
|
+
|
151
|
+
# Check whether all the given field names or bits are disabled (false or 0)
|
152
|
+
# On fields with more than one bit, will return true only if the value of the
|
153
|
+
# field is 0 (no bits set).
|
154
|
+
#
|
155
|
+
# @param [Symbol, Integer] fields one or more field names or bit indices
|
156
|
+
#
|
157
|
+
# @example Check fields to see if they are disabled
|
158
|
+
# # The struct is just an example, normally you would define a new class
|
159
|
+
# Example = Struct.new('Example', :flags)
|
160
|
+
# exo = Example.new(0)
|
161
|
+
# bits = Bits.new(exo, {:is_odd => 0, :amount => [1, 2, 3], :is_cool => 4})
|
162
|
+
# # we initialized flags to 0, so everything is disabled
|
163
|
+
# bits.disabled?(:is_odd) #=> true
|
164
|
+
# bits.disabled?(:amount, :is_cool) #=> true
|
165
|
+
# bits.disabled?(10, 5, :is_odd) #=> true
|
166
|
+
#
|
167
|
+
# # We now change flags on our instance object
|
168
|
+
# exo.flags = 5 # is_odd = 1, amount = 2, is_cool = 0
|
169
|
+
# bits.disabled?(:is_odd) #=> false
|
170
|
+
# bits.disabled?(:amount, :is_cool) #=> false
|
171
|
+
# bits.disabled?(:amount, :is_odd) #=> false
|
172
|
+
# bits.disabled?(:is_cool) #=> true
|
173
|
+
#
|
174
|
+
# @return [Boolean] true if ALL the given field bits are disabled (all bits
|
175
|
+
# are not set or false)
|
176
|
+
def disabled?(*fields)
|
177
|
+
memo = true
|
178
|
+
field = self.field
|
179
|
+
|
180
|
+
fields.flatten.each do |name|
|
181
|
+
break unless memo
|
182
|
+
memo &&= (read(name, field) == 0)
|
183
|
+
end
|
184
|
+
|
185
|
+
memo
|
186
|
+
end
|
187
|
+
|
188
|
+
# Get a BitField instance for the current value.
|
189
|
+
#
|
190
|
+
# Note: Value changes are NOT tracked and updated into the instance, so call
|
191
|
+
# this method directly as needed.
|
192
|
+
#
|
193
|
+
# @return [BitMagic::BitField] a BitField object with the current value
|
194
|
+
def field
|
195
|
+
BitField.new(self.value)
|
196
|
+
end
|
197
|
+
|
198
|
+
# Read a field or bit from its bit index or name
|
199
|
+
#
|
200
|
+
# @param [Symbol, Integer] name either the name of the bit (a key in field_list)
|
201
|
+
# or a integer bit position
|
202
|
+
# @param [BitField optional] field a specific BitField to read from.
|
203
|
+
# default: return value of #field
|
204
|
+
#
|
205
|
+
# @example Read bit values
|
206
|
+
# # The struct is just an example, normally you would define a new class
|
207
|
+
# Example = Struct.new('Example', :flags)
|
208
|
+
# exo = Example.new(9)
|
209
|
+
# bits = Bits.new(exo, {:is_odd => 0, :amount => [1, 2, 3], :is_cool => 4})
|
210
|
+
# bits.read(:is_odd) #=> 1
|
211
|
+
# bits.read(:amount) #=> 4
|
212
|
+
# bits.read(:is_cool) #=> 0
|
213
|
+
# bits.read(:amount, BitField.new(78)) #=> 7
|
214
|
+
# # Bonus: aliased as []
|
215
|
+
# bits[:is_odd] #=> 1
|
216
|
+
#
|
217
|
+
# @return [Integer] a value of the bit (0 or 1) or bits (number from 0 to
|
218
|
+
# (2**bit_length) - 1) or nil if the field name is not in the list
|
219
|
+
def read(name, field = nil)
|
220
|
+
field ||= self.field
|
221
|
+
|
222
|
+
if name.is_a?(Integer)
|
223
|
+
field.read_field(name)
|
224
|
+
elsif bits = @field_list[name]
|
225
|
+
field.read_field(bits)
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
alias :[] :read
|
230
|
+
|
231
|
+
# Write a field or bit from its field name or index
|
232
|
+
#
|
233
|
+
# Note: only the total bits of the field is used from the given value, so
|
234
|
+
# any additional bits are ignored. eg: writing a field with one bit as value
|
235
|
+
# of 4 will set the bit to 0, writing 5 sets it to 1.
|
236
|
+
#
|
237
|
+
# @param [Symbol, Integer, Array<Integer>] name a field name, or bit position,
|
238
|
+
# or array of bit positions
|
239
|
+
# @param [Integer, Array<Integer>] target_value the target value for the field
|
240
|
+
# (note: technically, this can be anything that responds to :[](index), but
|
241
|
+
# usage in that type of context is discouraged without adapter support)
|
242
|
+
#
|
243
|
+
# @example Write values to bit fields
|
244
|
+
# # The struct is just an example, normally you would define a new class
|
245
|
+
# Example = Struct.new('Example', :flags)
|
246
|
+
# exo = Example.new(0)
|
247
|
+
# bits = Bits.new(exo, {:is_odd => 0, :amount => [1, 2, 3], :is_cool => 4})
|
248
|
+
# bits.write(:is_odd, 1) #=> 1
|
249
|
+
# bits.write(:amount, 5) #=> 11
|
250
|
+
# exo.flags #=> 11
|
251
|
+
# # Bonus, aliased as :[]=, but note in this mode, the return value is same as given value
|
252
|
+
# bits[:is_cool] = 1 #=> 1
|
253
|
+
# exo.flags #=> 27
|
254
|
+
#
|
255
|
+
# @return the return value of the updater Proc, usually is equal to the final
|
256
|
+
# master value (with all the bits) after writing this bit
|
257
|
+
def write(name, target_value)
|
258
|
+
if name.is_a?(Symbol)
|
259
|
+
self.write(@field_list[name], target_value)
|
260
|
+
elsif name.is_a?(Integer)
|
261
|
+
self.update self.field.write_bits(name => @options[:bool_caster].call(target_value))
|
262
|
+
elsif name.respond_to?(:[]) and target_value.respond_to?(:[])
|
263
|
+
bits = {}
|
264
|
+
|
265
|
+
name.each_with_index do |bit, i|
|
266
|
+
bits[bit] = @options[:bool_caster].call(target_value[i])
|
267
|
+
end
|
268
|
+
|
269
|
+
self.update self.field.write_bits bits
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
alias :[]= :write
|
274
|
+
|
275
|
+
# Inspect output.
|
276
|
+
#
|
277
|
+
# @return [String] an #inspect value for this instance
|
278
|
+
def inspect
|
279
|
+
short_options = {}
|
280
|
+
short_options[:default] = @options[:default]
|
281
|
+
short_options[:attribute_name] = @options[:attribute_name]
|
282
|
+
"#<#{self.class.to_s} #{@magician ? "bit_magic=#{@magician.bit_magic_name.inspect} " : nil}value=#{self.value}> options=#{short_options.inspect}"
|
283
|
+
end
|
284
|
+
end
|
285
|
+
end
|
@@ -0,0 +1,399 @@
|
|
1
|
+
require_relative './bits'
|
2
|
+
|
3
|
+
module BitMagic
|
4
|
+
# This module generates integers that are bit representations of given bits,
|
5
|
+
# arrays of possible values with given bits, and arrays of possible values
|
6
|
+
# given some condition on the bits.
|
7
|
+
#
|
8
|
+
# In short, it gives you a list of possible integer values from a list of bits.
|
9
|
+
#
|
10
|
+
# @attr_reader [Hash] field_list a hash of :name => bit or :name => [bit, bit, ..., bits]
|
11
|
+
# key-value pairs for easier access of named bits/bit ranges
|
12
|
+
# @attr_reader [Hash] options options given to the generator
|
13
|
+
# @attr_reader [Integer] length the total number of bits specified
|
14
|
+
# @attr_reader [Integer] bits an array of bits used in field_list, list of
|
15
|
+
# all the bits with which we will be working with
|
16
|
+
#
|
17
|
+
class BitsGenerator
|
18
|
+
attr_reader :field_list, :options, :length, :bits
|
19
|
+
DEFAULT_OPTIONS = {default: 0, bool_caster: Bits::BOOLEAN_CASTER}.freeze
|
20
|
+
|
21
|
+
# Initialize the generator.
|
22
|
+
#
|
23
|
+
# @param [BitMagic::Adapters::Magician, Hash] magician_or_field_list a Magician
|
24
|
+
# object that contains field_list, bits, and options OR a Hash object of
|
25
|
+
# :name => bit index or bit index array, key-value pairs
|
26
|
+
# @param [Hash] options options and defaults, will override Magician action_options
|
27
|
+
# if given
|
28
|
+
# @option options [Integer] :default a default value, default: 0
|
29
|
+
# @option options [Proc] :bool_caster a callable Method, Proc or lambda that
|
30
|
+
# is used to cast a value into a boolean
|
31
|
+
#
|
32
|
+
# @return [BitsGenerator] the resulting object
|
33
|
+
def initialize(magician_or_field_list, options = {})
|
34
|
+
if defined?(BitMagic::Adapters::Magician) and magician_or_field_list.is_a?(BitMagic::Adapters::Magician)
|
35
|
+
@magician = magician_or_field_list
|
36
|
+
@field_list = @magician.field_list
|
37
|
+
@options = @magician.action_options.merge(options)
|
38
|
+
@length = @magician.bits_length
|
39
|
+
@bits = @magician.bits.uniq
|
40
|
+
else
|
41
|
+
@field_list = magician_or_field_list
|
42
|
+
@options = DEFAULT_OPTIONS.merge(options)
|
43
|
+
@bits = @field_list.values.flatten.uniq
|
44
|
+
@length = @bits.length
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Given a field name or list of field names, return their corresponding bits
|
49
|
+
# Field names are the key values of the field_list hash during initialization
|
50
|
+
#
|
51
|
+
# @param [Symbol, Integer] one or more keys for the field name or an integer
|
52
|
+
# for a known bit index
|
53
|
+
#
|
54
|
+
# @example Get a list of bits
|
55
|
+
# gen = BitMagic::BitsGenerator.new(:is_odd => 0, :amount => [1, 2, 3], :is_cool => 4)
|
56
|
+
# gen.bits_for(:is_odd) #=> [0]
|
57
|
+
# gen.bits_for(:amount) #=> [1, 2, 3]
|
58
|
+
# gen.bits_for(:is_odd, :amount) #=> [0, 1, 2, 3]
|
59
|
+
# gen.bits_for(:is_cool, 5, 6) #=> [4, 5, 6]
|
60
|
+
# gen.bits_for(9, 10) #=> [9, 10]
|
61
|
+
#
|
62
|
+
# @return [Array<Integer>] an array of bit indices
|
63
|
+
def bits_for(*field_names)
|
64
|
+
bits = []
|
65
|
+
|
66
|
+
field_names.flatten.each do |i|
|
67
|
+
if i.is_a?(Integer)
|
68
|
+
bits << i
|
69
|
+
next
|
70
|
+
end
|
71
|
+
|
72
|
+
if i.respond_to?(:to_sym) and @field_list[i.to_sym]
|
73
|
+
bits << @field_list[i.to_sym]
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
bits.flatten
|
78
|
+
end
|
79
|
+
|
80
|
+
# Iterates over the entire combination of all possible values utilizing the
|
81
|
+
# list of bits we are given.
|
82
|
+
#
|
83
|
+
# Warning: Because we are iteration over possible values, the total available
|
84
|
+
# values grows exponentially with the given number of bits. For example, if
|
85
|
+
# you use only 8 bits, there are 2*8 = 256 possible values, with 20 bits it
|
86
|
+
# grows to 2**20 = 1048576. At 32 bits, 2**32 = 4294967296.
|
87
|
+
#
|
88
|
+
# Warning 2: We're using combinations to generate each individual number, so
|
89
|
+
# there's additional overhead causing O(n!) complexity. Use carefully when
|
90
|
+
# you have large bit lists (more than 16 bits total). Check timing and memory.
|
91
|
+
#
|
92
|
+
# @param [optional, Array<Integer>] each_bits a list of bits used to generate
|
93
|
+
# the combination list. default: the list of bits given during initialization
|
94
|
+
# @param [Proc] block a callable method to yield individual values
|
95
|
+
#
|
96
|
+
# @yield num will yield to the given block multiple times, each time with one
|
97
|
+
# of the integer values that are possible from the given bits list
|
98
|
+
#
|
99
|
+
# @example Iterate over a list of bits
|
100
|
+
# gen = BitsGenerator.new(:is_odd => 0, :amount => [1, 2, 3], :is_cool => 4)
|
101
|
+
# values = []
|
102
|
+
# gen.each_value { |val| values << val } #=> 32
|
103
|
+
# values #=> [0, 1, 2, 4, 8, 16, 3, 5, 9, 17, 6, 10, 18, 12, 20, 24, 7, 11, 19, 13, 21, 25, 14, 22, 26, 28, 15, 23, 27, 29, 30, 31]
|
104
|
+
# values2 = []
|
105
|
+
# gen.each_value([0, 5]) { |val| values2 << val } #=> 4
|
106
|
+
# values2 #=> [0, 1, 32, 33]
|
107
|
+
#
|
108
|
+
#
|
109
|
+
# @return [Integer] total number of values yielded
|
110
|
+
def each_value(each_bits = nil, &block)
|
111
|
+
# Warning! This has exponential complexity (time and space)
|
112
|
+
# 2**n to be precise, use sparingly
|
113
|
+
|
114
|
+
yield 0
|
115
|
+
count = 1
|
116
|
+
|
117
|
+
if @options[:default] != 0
|
118
|
+
yield @options[:default]
|
119
|
+
count += 1
|
120
|
+
end
|
121
|
+
|
122
|
+
each_bits = self.bits if each_bits == nil
|
123
|
+
|
124
|
+
1.upto(each_bits.length).each do |i|
|
125
|
+
each_bits.combination(i).each do |bits_list|
|
126
|
+
num = bits_list.reduce(0) { |m, j| m |= (1 << j) }
|
127
|
+
yield num
|
128
|
+
count += 1
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
count
|
133
|
+
end
|
134
|
+
|
135
|
+
# Gives you an array of all possible integer values based off the bit
|
136
|
+
# combinations of the bit list.
|
137
|
+
#
|
138
|
+
# Note: This will include all possible values from the bit list, but there
|
139
|
+
# are no guarantees on their order. If you need an ordered list, sort the result.
|
140
|
+
#
|
141
|
+
# Warning: Please see the warnings on each_value.
|
142
|
+
#
|
143
|
+
# Warning: Memory usage grows exponentially to the number of bits! For example,
|
144
|
+
# on a 64 bit platform (assuming pointers are 8 bytes) if you have 8 bits,
|
145
|
+
# this array will have 256 values, taking up 2KB of memory. At 20 bits, it's
|
146
|
+
# 1048576 values, taking up 8MB. At 32 bits, 4294967296 values take up 34GB!
|
147
|
+
#
|
148
|
+
# @param [optional, Array<Integer>] each_bits a list of bits used to generate
|
149
|
+
# the combination list. default: the list of bits given during initialization
|
150
|
+
# @param [Hash] opts additional options
|
151
|
+
# @option opts [Integer] :warn_threshold will output warning messages if
|
152
|
+
# the total number of bits is above this number. false to disable. default: 20
|
153
|
+
#
|
154
|
+
# @example Get an array for all values from our bit list
|
155
|
+
# gen = BitsGenerator.new(:is_odd => 0, :amount => [1, 2, 3], :is_cool => 4)
|
156
|
+
# gen.all_values
|
157
|
+
#
|
158
|
+
# @return [Array<Integer>] an array of all possible values from the bit list
|
159
|
+
# Order is not guaranteed to be consistent.
|
160
|
+
def all_values(each_bits = nil, opts = {warn_threshold: 12})
|
161
|
+
# Shuffle things around so that people can call #all_values(warn_threshold: false)
|
162
|
+
if each_bits.is_a?(Hash)
|
163
|
+
opts = each_bits
|
164
|
+
each_bits = nil
|
165
|
+
end
|
166
|
+
|
167
|
+
each_bits = self.bits if each_bits == nil
|
168
|
+
|
169
|
+
if opts[:warn_threshold] and each_bits.length > opts[:warn_threshold]
|
170
|
+
warn "There are #{each_bits.length} bits. You will have #{2**(each_bits.length)} values in the result. Please carefully benchmark the execution time and memory usage of your use-case."
|
171
|
+
warn "You can disable this warning by using #all_values(warn_threshold: false)"
|
172
|
+
end
|
173
|
+
|
174
|
+
values = []
|
175
|
+
|
176
|
+
self.each_value(each_bits) {|num| values << num }
|
177
|
+
|
178
|
+
values
|
179
|
+
end
|
180
|
+
|
181
|
+
# Gives you an array of values where at least one of the bits of the field
|
182
|
+
# names list is set to true (ie any of the bits are true).
|
183
|
+
#
|
184
|
+
# Possible values are derived from the bits list during initialization.
|
185
|
+
#
|
186
|
+
# Note: Order is not guaranteed. All numbers will be present, but there is
|
187
|
+
# no expectation that the numbers will be in the same order every time. If
|
188
|
+
# you need an ordered list, you can sort the result.
|
189
|
+
#
|
190
|
+
# @param [Symbol, Integer] one or more keys for the field name or an integer
|
191
|
+
# for a known bit index
|
192
|
+
#
|
193
|
+
# @example Retrieve a list for odd numbers or cool numbers
|
194
|
+
# gen = BitsGenerator.new(:is_odd => 0, :amount => [1, 2, 3], :is_cool => 4)
|
195
|
+
# gen.any_of(:is_odd) #=> [1, 3, 5, 9, 17, 7, 11, 19, 13, 21, 25, 15, 23, 27, 29, 31]
|
196
|
+
# gen.any_of(:is_cool) #=> [16, 17, 18, 20, 24, 19, 21, 25, 22, 26, 28, 23, 27, 29, 30, 31]
|
197
|
+
# gen.any_of(:is_odd, :is_cool) # is_odd or is_cool, same as union of arrays above
|
198
|
+
# #=> [1, 16, 3, 5, 9, 17, 18, 20, 24, 7, 11, 19, 13, 21, 25, 22, 26, 28, 15, 23, 27, 29, 30, 31]
|
199
|
+
#
|
200
|
+
#
|
201
|
+
# @return [Array<Integer>] an array of integer values with at least one of
|
202
|
+
# the bits set (true, bit is 1). Will be blank if no field names given.
|
203
|
+
def any_of(*field_names)
|
204
|
+
[].tap do |list|
|
205
|
+
any_num = any_of_number(*field_names)
|
206
|
+
self.each_value { |num| list << num if (num & any_num) > 0 }
|
207
|
+
end
|
208
|
+
end
|
209
|
+
alias :with_any :any_of
|
210
|
+
|
211
|
+
# Gives you an array of values where all of these bits of the field names
|
212
|
+
# list is set to false (ie without all of these bits as true).
|
213
|
+
#
|
214
|
+
# Possible values are derived from the bits list during initialization.
|
215
|
+
#
|
216
|
+
# Note: Order is not guaranteed. All numbers will be present, but there is
|
217
|
+
# no expectation that the numbers will be in the same order every time. If
|
218
|
+
# you need an ordered list, you can sort the result.
|
219
|
+
#
|
220
|
+
# @param [Symbol, Integer] one or more keys for the field name or an integer
|
221
|
+
# for a known bit index
|
222
|
+
#
|
223
|
+
# @example Retrieve a list for even numbers, or uncool numbers
|
224
|
+
# gen = BitsGenerator.new(:is_odd => 0, :amount => [1, 2, 3], :is_cool => 4)
|
225
|
+
# gen.none_of(:is_odd) #=> [0, 2, 4, 8, 16, 6, 10, 18, 12, 20, 24, 14, 22, 26, 28, 30]
|
226
|
+
# gen.none_of(:is_cool) #=> [0, 1, 2, 4, 8, 3, 5, 9, 6, 10, 12, 7, 11, 13, 14, 15]
|
227
|
+
# gen.none_of(:is_odd, :is_cool) #=> [0, 2, 4, 8, 6, 10, 12, 14]
|
228
|
+
# gen.none_of(:is_odd, :is_cool, :amount) #=> [0]
|
229
|
+
#
|
230
|
+
# @return [Array<Integer>] an array of integer values with all bits of given
|
231
|
+
# field names unset (false, bit is 0). Will be blank if no field names given.
|
232
|
+
def none_of(*field_names)
|
233
|
+
[].tap do |list|
|
234
|
+
lack_num = any_of_number(*field_names)
|
235
|
+
self.each_value { |num| list << num if (num & lack_num) == 0 }
|
236
|
+
end
|
237
|
+
end
|
238
|
+
alias :without_all :none_of
|
239
|
+
|
240
|
+
# Gives you an array of values where at least one of the bits of the field
|
241
|
+
# names list is set to false (ie without any of these bits as true).
|
242
|
+
#
|
243
|
+
# Possible values are derived from the bits list during initialization.
|
244
|
+
#
|
245
|
+
# Note: Order is not guaranteed. All numbers will be present, but there is
|
246
|
+
# no expectation that the numbers will be in the same order every time. If
|
247
|
+
# you need an ordered list, you can sort the result.
|
248
|
+
#
|
249
|
+
# @param [Symbol, Integer] one or more keys for the field name or an integer
|
250
|
+
# for a known bit index
|
251
|
+
#
|
252
|
+
# @example Retrieve a list for even numbers, and uncool numbers
|
253
|
+
# gen = BitsGenerator.new(:is_odd => 0, :amount => [1, 2, 3], :is_cool => 4)
|
254
|
+
# gen.instead_of(:is_odd) #=> [0, 2, 4, 8, 16, 6, 10, 18, 12, 20, 24, 14, 22, 26, 28, 30]
|
255
|
+
# gen.instead_of(:is_odd, :is_cool) #=> [0, 1, 2, 4, 8, 16, 3, 5, 9, 6, 10, 18, 12, 20, 24, 7, 11, 13, 14, 22, 26, 28, 15, 30]
|
256
|
+
# gen.instead_of(:is_odd, :is_cool, :amount) #=> [0, 1, 2, 4, 8, 16, 3, 5, 9, 17, 6, 10, 18, 12, 20, 24, 7, 11, 19, 13, 21, 25, 14, 22, 26, 28, 15, 23, 27, 29, 30]
|
257
|
+
#
|
258
|
+
# @return [Array<Integer>] an array of integer values with at least one of
|
259
|
+
# the bits unset (false, bit is 0). Will be blank if no field names given.
|
260
|
+
def instead_of(*field_names)
|
261
|
+
[].tap do |list|
|
262
|
+
none_num = any_of_number(*field_names)
|
263
|
+
self.each_value { |num| list << num if (num & none_num) != none_num }
|
264
|
+
end
|
265
|
+
end
|
266
|
+
alias :without_any :instead_of
|
267
|
+
|
268
|
+
def all_of(*field_names)
|
269
|
+
[].tap do |list|
|
270
|
+
all_num = any_of_number(*field_names)
|
271
|
+
self.each_value { |num| list << num if (num & all_num) == all_num }
|
272
|
+
end
|
273
|
+
end
|
274
|
+
alias :with_all :all_of
|
275
|
+
|
276
|
+
# Gives you an array of values where the given field names are exactly
|
277
|
+
# equal to their given field values.
|
278
|
+
#
|
279
|
+
# Possible values are derived from the bits list during initialization.
|
280
|
+
#
|
281
|
+
# Note: Order is not guaranteed. All numbers will be present, but there is
|
282
|
+
# no expectation that the numbers will be in the same order every time. If
|
283
|
+
# you need an ordered list, you can sort the result.
|
284
|
+
#
|
285
|
+
# @param [Hash] one or more field names or an integer for a known bit index
|
286
|
+
# as the key, and the value (integer or boolean) for that field as the hash
|
287
|
+
# value. Values that have more bits than available will be truncated for
|
288
|
+
# comparison. eg. a field with one bit setting value to 2 means field is 0
|
289
|
+
#
|
290
|
+
# @example Get different amount values
|
291
|
+
# gen = BitsGenerator.new(:is_odd => 0, :amount => [1, 2, 3], :is_cool => 4)
|
292
|
+
# gen.equal_to(:amount => 5) #=> [10, 11, 26, 27]
|
293
|
+
# gen.equal_to(:amount => 7) #=> [14, 15, 30, 31]
|
294
|
+
# gen.equal_to(:amount => 7, :is_odd => 1, :is_cool => 1) #=> [31]
|
295
|
+
#
|
296
|
+
# @return [Array<Integer>] an array of integer values where the bits and values
|
297
|
+
# match the given input
|
298
|
+
def equal_to(field_values = {})
|
299
|
+
all_num, none_num = self.equal_to_numbers(field_values)
|
300
|
+
|
301
|
+
[].tap do |list|
|
302
|
+
self.each_value { |num| list << num if (num & all_num) == all_num and (num & none_num) == 0 }
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
# Will return an array of two numbers, the first of which has all bits set
|
307
|
+
# where the corresponding value bit is 1, and the second has all bits set
|
308
|
+
# where the corresponding value bit is 0.
|
309
|
+
# These numbers can be used in advanced bitwise operations to test fields
|
310
|
+
# for exact equality.
|
311
|
+
#
|
312
|
+
# @param [Hash] one or more field names or an integer for a known bit index
|
313
|
+
# as the key, and the value (integer or boolean) for that field as the hash
|
314
|
+
# value. Values that have more bits than available will be truncated for
|
315
|
+
# comparison. eg. a field with one bit setting value to 2 means field is 0
|
316
|
+
#
|
317
|
+
# @example Retrieve the representation for various amounts
|
318
|
+
# gen = BitsGenerator.new(:is_odd => 0, :amount => [1, 2, 3], :is_cool => 4)
|
319
|
+
# gen.equal_to_numbers(:amount => 5) #=> [10, 4]
|
320
|
+
# # 5 is 101, 10 is 1010 and 4 is 100. (Note that amount uses bits 1, 2, 3)
|
321
|
+
# gen.equal_to_numbers(:amount => 7) #=> [14, 0]
|
322
|
+
# gen.equal_to_numbers(:amount => 7, :is_odd => 1) #=> [15, 0]
|
323
|
+
#
|
324
|
+
#
|
325
|
+
# @return [Array<Integer>] an array of two integers, first representing bits
|
326
|
+
# of given field bit values as 1 set to 1, and the second representing bits
|
327
|
+
# of given field bit values as 0 set to 1. See the example.
|
328
|
+
def equal_to_numbers(field_values = {})
|
329
|
+
fields = {}
|
330
|
+
|
331
|
+
field_values.each_pair do |field_name, v|
|
332
|
+
bits = self.bits_for(field_name)
|
333
|
+
fields[bits] = v if bits.length > 0
|
334
|
+
end
|
335
|
+
|
336
|
+
all_num = 0
|
337
|
+
none_num = 0
|
338
|
+
fields.each_pair { |field_bits, val|
|
339
|
+
field_bits.each_with_index do |bit, i|
|
340
|
+
if @options[:bool_caster].call(val[i])
|
341
|
+
all_num |= (1 << bit)
|
342
|
+
else
|
343
|
+
none_num |= (1 << bit)
|
344
|
+
end
|
345
|
+
end
|
346
|
+
}
|
347
|
+
|
348
|
+
[all_num, none_num]
|
349
|
+
end
|
350
|
+
|
351
|
+
# Get an integer with the given field names' bits all set. This number can
|
352
|
+
# be used in bitwise operations to test field conditionals.
|
353
|
+
#
|
354
|
+
# @param [Symbol, Integer] one or more keys for the field name or an integer
|
355
|
+
# for a known bit index
|
356
|
+
#
|
357
|
+
# @example Get a number representing odd numbers, or an arbitrary amount
|
358
|
+
# gen = BitsGenerator.new(:is_odd => 0, :amount => [1, 2, 3], :is_cool => 4)
|
359
|
+
# gen.any_of_number(:is_odd) #=> 1 # because 1 in binary is 00001
|
360
|
+
# gen.any_of_number(:amount) #=> 14 # because 14 is 01110
|
361
|
+
# gen.any_of_number(:is_odd, :amount, :is_cool) #=> 31
|
362
|
+
# # 31 is 11111 because all five bits (0 to 4) are true (one)
|
363
|
+
#
|
364
|
+
#
|
365
|
+
# @return [Integer] the integer with all field names' bits are true
|
366
|
+
def any_of_number(*field_names)
|
367
|
+
self.bits_for(*field_names).reduce(0) { |m, bit| m | (1 << bit) }
|
368
|
+
end
|
369
|
+
alias :with_any_number :any_of_number
|
370
|
+
alias :with_all_number :any_of_number
|
371
|
+
alias :all_of_number :any_of_number
|
372
|
+
|
373
|
+
# Get an integer with the given field names' bits all unset. This number can
|
374
|
+
# be used in bitwise operations to test field conditionals.
|
375
|
+
#
|
376
|
+
# Note: Because of ruby's handling of two's complement, this number is
|
377
|
+
# almost always a negative number.
|
378
|
+
#
|
379
|
+
# @param [Symbol, Integer] one or more keys for the field name or an integer
|
380
|
+
# for a known bit index
|
381
|
+
#
|
382
|
+
# @example Get a number representing even numbers, or an arbitrary amount
|
383
|
+
# gen = BitsGenerator.new(:is_odd => 0, :amount => [1, 2, 3], :is_cool => 4)
|
384
|
+
# gen.none_of_number(:is_odd) #=> -2 # because -2 in binary is 1...11110
|
385
|
+
# gen.none_of_number(:amount) #=> -15
|
386
|
+
# # -15 in binary is 1...10001
|
387
|
+
# gen.none_of_number(:is_odd, :amount, :is_cool) #=> -32
|
388
|
+
# # -32 is 1...00000 because all five bits (0 to 4) are false (zero)
|
389
|
+
#
|
390
|
+
#
|
391
|
+
# @return [Integer] the integer with all the field names' bits set to false
|
392
|
+
def none_of_number(*field_names)
|
393
|
+
~self.any_of_number(*field_names)
|
394
|
+
end
|
395
|
+
alias :without_any_number :none_of_number
|
396
|
+
alias :without_all_number :none_of_number
|
397
|
+
|
398
|
+
end
|
399
|
+
end
|