bit_magic 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|