Class: Nodepile::KeyedArrayAccessor
- Inherits:
-
Object
- Object
- Nodepile::KeyedArrayAccessor
- Includes:
- Enumerable
- Defined in:
- lib/nodepile/keyed_array.rb
Overview
Class makes an array of values behave like a hash. Intended to be used for rendering records from a tabular data source.
Defined Under Namespace
Classes: PseudoROHashForDeconstruct
Instance Attribute Summary collapse
-
#extensible ⇒ Object
Returns the value of attribute extensible.
-
#ref_num ⇒ Object
Returns the value of attribute ref_num.
-
#source ⇒ Object
Returns the value of attribute source.
Class Method Summary collapse
-
.bulk_overlay(kaa_enumerable) ⇒ KeyedArrayAccessor?
Repeatedly overlays successive KeyedArrayAccessor with the first one being at the bottom and the last one being at the top.
Instance Method Summary collapse
-
#==(otr) ⇒ Object
Equality comparison is very tolerant and may not be what you expect.
-
#[](key) ⇒ Object
Provides hash-style access to a value by it’s key (rather than its position).
-
#[]=(key, new_val) ⇒ Object
Uses a hash-style access to update values.
-
#clear?(*key_names) ⇒ Boolean
Returns true if the value for each provided key is nil.
-
#clear_blanks ⇒ self
clear blanks will replace all fields where the value is pure whitespace with a nil instead.
-
#cleared ⇒ Object
Return a copy of self where all values have been cleared.
-
#conforms?(otr) ⇒ Boolean
indicated that the object has exactly the same keys in exactly the same order.
-
#deconstruct_keys(keys) ⇒ Object
Note that deconstruct_keys will expose metadata values regardless of the choice of @metadata_key_prefix.
-
#dup ⇒ Object
Copy self, including metadata, source, and ref_num.
- #each {|key, value| ... } ⇒ Object
-
#each_empty_key(yield_index = false) ⇒ Void, Enumerator
An empty key is a key whose value is nil.
-
#each_filled_pair(yield_index_instead_of_val = false) ⇒ Void, Enumerator
A filled key is a key whose value is not nil.
- #each_key ⇒ Object
- #each_value(&block) ⇒ Object
- #include?(key) ⇒ Boolean
-
#initialize(keys_array, values_array, extensible: true, source: nil, ref_num: nil, metadata: nil, metadata_key_prefix: '') ⇒ KeyedArrayAccessor
constructor
Note that this method will always freeze and retain a reference to the keys_array that is passed in.
- #keys ⇒ Object
-
#kv_map!(&kv_receiver) ⇒ void
Similar to a Hash’s #map! function.
- #length ⇒ Object
- #merge(otr_hashlike) ⇒ Object
-
#merge!(otr_hashlike) ⇒ Object
Other object must support Note: metadata is left unchanged by this method.
-
#metadata(key) ⇒ Object
retrieve metadata value.
- #metadata_include?(key) ⇒ Boolean
- #metadata_key_prefix ⇒ Object
-
#overlay(lower_kaa) ⇒ Object
See #overlay!() except this creates a copy rather than altering self.
-
#overlay!(lower_kaa) ⇒ self
See #underlay() An important difference between overlay and underlay is the ordering of columns in the result.
-
#reset_metadata(pair_enumerable, metadata_key_prefix: :leave_prefix_unchanged) ⇒ void
Dump any existing metadata and replace it with the provided metadata.
-
#size ⇒ Object
Note that duplications of the same key are counted toward this number.
-
#to_a ⇒ Object
alias for #values.
-
#to_h ⇒ Object
Note that if the same key name is duplicated multiple times, the leftmost value is used.
- #underlay(upper_kaa) ⇒ Object
-
#underlay!(upper_kaa) ⇒ self
Given a KeyedArrayAccessor objects, update self to form a “merged” KeyedArrayAccessor where self “underlays” an “upper” array to generate a merged data structure.
- #update_metadata(key, value) ⇒ Object
- #value_at(index) ⇒ Object
- #values ⇒ Object
Constructor Details
#initialize(keys_array, values_array, extensible: true, source: nil, ref_num: nil, metadata: nil, metadata_key_prefix: '') ⇒ KeyedArrayAccessor
Note that this method will always freeze and retain a reference to the keys_array that is passed in. Note that this method may use a reference to the values_array passed in (allowing later mutation). See the copy parameter for override.
47 48 49 50 51 52 53 54 55 56 57 |
# File 'lib/nodepile/keyed_array.rb', line 47 def initialize(keys_array, values_array, extensible: true,source: nil, ref_num: nil, metadata: nil, metadata_key_prefix: '' ) raise "keys must all be of type String or nil" unless keys_array.all?{|k| k.nil? || k.is_a?(String)} @keys = keys_array.freeze @vals = values_array || Array.new(@keys.length){nil} @extensible = extensible @source = source @ref_num = ref_num (,metadata_key_prefix: ) if end |
Instance Attribute Details
#extensible ⇒ Object
Returns the value of attribute extensible.
8 9 10 |
# File 'lib/nodepile/keyed_array.rb', line 8 def extensible @extensible end |
#ref_num ⇒ Object
Returns the value of attribute ref_num.
101 102 103 |
# File 'lib/nodepile/keyed_array.rb', line 101 def ref_num @ref_num end |
#source ⇒ Object
Returns the value of attribute source.
101 102 103 |
# File 'lib/nodepile/keyed_array.rb', line 101 def source @source end |
Class Method Details
.bulk_overlay(kaa_enumerable) ⇒ KeyedArrayAccessor?
Repeatedly overlays successive KeyedArrayAccessor with the first one being at the bottom and the last one being at the top.
343 344 345 |
# File 'lib/nodepile/keyed_array.rb', line 343 def self.(kaa_enumerable) kaa_enumerable.inject(nil){|accum,kaa| (accum||kaa.dup).underlay!(kaa) } end |
Instance Method Details
#==(otr) ⇒ Object
Equality comparison is very tolerant and may not be what you expect.
-
Hashes are deemed equal if they have the same unique keys and the key-value pairs retrieved via #[] are equal.
-
Arrays are deemed equal if the to_a() representation of self matches the other array.
-
Another KeyedArrayAccessor is deemed equal using one of two rules. For #conforms? true objects, the exact value of keys and values is compared. For #conforms? false objects, the set of distinct keys is the same in both arrays and the value associated with each key using #[] is the same.
Another KeyedAccessArray is deemed equal if the key-value pairs have the same number of keys and are equal (which means that only the first of duplicate columns is compared)
Note: Metadata is not considered for purposes of this comparison.
205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 |
# File 'lib/nodepile/keyed_array.rb', line 205 def ==(otr) return true if self.equal?(otr) case otr in Hash return ((@keys + otr.keys)-(@keys&otr.keys)).empty? && otr.all?{|k,v| self[k] == v} in Array return @vals == otr in KeyedArrayAccessor if self.conforms?(otr) return @vals == otr._internal_vals else return ((@keys + otr._internal_keys) - (@keys & otr._internal_keys)).empty? && @keys.all?{|k| self[k] == otr[k]} end else return false # currently no other types are supported end #pattern match end |
#[](key) ⇒ Object
Provides hash-style access to a value by it’s key (rather than its position). Note that if duplicate keys exist, the leftmost key is returned. Returns the value of the key or quietly returns nil if the key isn’t found
Note, if the object has metadata, and the metadata_key_prefix is not nil, this method will attempt to retrieve metadata matching the key before retrieving the normal key data. If the metadata does not contain the requested key, this will check for a match of the normal data.
247 248 249 250 |
# File 'lib/nodepile/keyed_array.rb', line 247 def [](key) return @meta[key] if @meta_key_prefix && key.start_with?(@meta_key_prefix) && @meta&.include?(key) @keys.index(key)&.tap{|ix| return @vals[ix]} end |
#[]=(key, new_val) ⇒ Object
Uses a hash-style access to update values. Note that becuase this data structure does not enforce uniqueness of keys, this method will only update the leftmost value corresponding to the given key.
Important note. Adding a new key to the object will make it non-conforming with other objects.
259 260 261 262 263 264 265 266 267 268 269 270 271 272 |
# File 'lib/nodepile/keyed_array.rb', line 259 def []=(key,new_val) ix = @keys.index(key) if ix (@vals[ix] = new_val) if ix # simple case... update existing value else # ix.nil? raise <<~ERRMSG if !extensible Because the #extensible() attribute is set to false, a new key may not be added [#{key}] ERRMSG # new keys are appended to the right side @keys = (@keys.dup << key) @vals << new_val end return new_val end |
#clear?(*key_names) ⇒ Boolean
Returns true if the value for each provided key is nil
117 118 119 120 |
# File 'lib/nodepile/keyed_array.rb', line 117 def clear?(*key_names) key_names.flatten! return key_names.all?{|k| self[k].nil?} end |
#clear_blanks ⇒ self
clear blanks will replace all fields where the value is pure whitespace with a nil instead. Note that nils get special treatment in operations like #overlay()
111 112 113 114 |
# File 'lib/nodepile/keyed_array.rb', line 111 def clear_blanks() @vals.transform_values{|v| (v.is_a?(String) && /^\s*$/.match?(v)) ? nil : v } return self end |
#cleared ⇒ Object
Return a copy of self where all values have been cleared. Metadata is
105 |
# File 'lib/nodepile/keyed_array.rb', line 105 def cleared() = self.class.new(@keys,Array.new(@keys.length){nil}) |
#conforms?(otr) ⇒ Boolean
indicated that the object has exactly the same keys in exactly the same order
276 277 278 |
# File 'lib/nodepile/keyed_array.rb', line 276 def conforms?(otr) return otr.is_a?(self.class) && @keys == otr._internal_keys end |
#deconstruct_keys(keys) ⇒ Object
Note that deconstruct_keys will expose metadata values regardless of the choice of @metadata_key_prefix. Although, an actual key with the same name as a metadata value will hide the metadata value.
361 362 363 364 365 |
# File 'lib/nodepile/keyed_array.rb', line 361 def deconstruct_keys(keys) # Developer note: I think the below line should work as desired because this object is so # much like a hash, but possibly it'll be necessary to support one or more methods. return PseudoROHashForDeconstruct.new(self) end |
#dup ⇒ Object
Copy self, including metadata, source, and ref_num
60 61 62 63 64 |
# File 'lib/nodepile/keyed_array.rb', line 60 def dup self.class.new(@keys,@vals.dup,extensible: @extensible, metadata: @meta, source: @source, ref_num: @ref_num,metadata_key_prefix: @meta_key_prefix ) end |
#each {|key, value| ... } ⇒ Object
148 149 150 151 |
# File 'lib/nodepile/keyed_array.rb', line 148 def each return enum_for(:each) unless block_given? @keys.each_with_index{|k,i| yield(k,@vals[i]) } end |
#each_empty_key(yield_index = false) ⇒ Void, Enumerator
An empty key is a key whose value is nil.
163 164 165 166 167 |
# File 'lib/nodepile/keyed_array.rb', line 163 def each_empty_key(yield_index = false) return enum_for(:each_key_blank, yield_index) unless block_given? @keys.each_with_index{|k,i| yield(yield_index ? i : k) if @vals[i].nil?} return nil end |
#each_filled_pair(yield_index_instead_of_val = false) ⇒ Void, Enumerator
A filled key is a key whose value is not nil. The block is yielded with the key and value (or index and value depending on yield_index parameter).
175 176 177 178 179 |
# File 'lib/nodepile/keyed_array.rb', line 175 def each_filled_pair(yield_index_instead_of_val = false) return enum_for(:each_key_nonblank, yield_index_instead_of_val) unless block_given? @keys.each_with_index{|k,i| yield((yield_index_instead_of_val ? i : k),@vals[i]) unless @vals[i].nil?} return nil end |
#each_key ⇒ Object
134 135 136 137 |
# File 'lib/nodepile/keyed_array.rb', line 134 def each_key return enum_for(:each_key) unless block_given? @keys.each{|k| yield k } end |
#each_value(&block) ⇒ Object
153 154 155 156 |
# File 'lib/nodepile/keyed_array.rb', line 153 def each_value(&block) return enum_for(:each_value) unless block_given? @vals.each{|v| yield(v)} end |
#include?(key) ⇒ Boolean
228 |
# File 'lib/nodepile/keyed_array.rb', line 228 def include?(key) = return @keys.include?(key) |
#keys ⇒ Object
131 |
# File 'lib/nodepile/keyed_array.rb', line 131 def keys = @keys |
#kv_map!(&kv_receiver) ⇒ void
This method returns an undefined value.
Similar to a Hash’s #map! function
141 142 143 144 145 |
# File 'lib/nodepile/keyed_array.rb', line 141 def kv_map!(&kv_receiver) raise "Block required" unless block_given? @keys.each_with_index{|k,i| @vals[i] = yield(k,@vals[i])} return nil end |
#length ⇒ Object
189 |
# File 'lib/nodepile/keyed_array.rb', line 189 def length = self.size |
#merge(otr_hashlike) ⇒ Object
237 |
# File 'lib/nodepile/keyed_array.rb', line 237 def merge(otr_hashlike) = self.dup.merge!(otr_hashlike) |
#merge!(otr_hashlike) ⇒ Object
Other object must support Note: metadata is left unchanged by this method.
232 233 234 235 236 |
# File 'lib/nodepile/keyed_array.rb', line 232 def merge!(otr_hashlike) raise "Block handling not yet supported by this method" if block_given? otr_hashlike.each_key{|k| self[k] = otr_hashlike[k]} return self end |
#metadata(key) ⇒ Object
retrieve metadata value
97 |
# File 'lib/nodepile/keyed_array.rb', line 97 def (key) = @meta[key] |
#metadata_include?(key) ⇒ Boolean
98 |
# File 'lib/nodepile/keyed_array.rb', line 98 def (key) = @meta.include?(key) |
#metadata_key_prefix ⇒ Object
99 |
# File 'lib/nodepile/keyed_array.rb', line 99 def = @metadata_key_prefix |
#overlay(lower_kaa) ⇒ Object
See #overlay!() except this creates a copy rather than altering self.
315 |
# File 'lib/nodepile/keyed_array.rb', line 315 def (lower_kaa) = lower_kaa.underlay(self) |
#overlay!(lower_kaa) ⇒ self
See #underlay() An important difference between overlay and underlay is the ordering of columns in the result. Column order is in the order of the lower object plus any (non-nil) additions appearing to the right. This operation is not particularly efficient except when working with conforming arrays.
Note: metadata is unchanged by this method.
327 328 329 330 331 332 333 334 335 336 337 338 |
# File 'lib/nodepile/keyed_array.rb', line 327 def (lower_kaa) return self if self.equal?(lower_kaa) #no-op if conforms?(lower_kaa) lower_kaa._each_value_with_index(true){|lower_val,ix| @vals[ix] ||= lower_val} else new_arr = lower_kaa.dup.underlay!(self) @keys = new_arr._internal_keys @vals = new_arr._internal_vals @key_count = nil end return self end |
#reset_metadata(pair_enumerable, metadata_key_prefix: :leave_prefix_unchanged) ⇒ void
This method returns an undefined value.
Dump any existing metadata and replace it with the provided metadata
69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
# File 'lib/nodepile/keyed_array.rb', line 69 def (pair_enumerable, metadata_key_prefix: :leave_prefix_unchanged) @meta_key_prefix = unless == :leave_prefix_unchanged pfx = @metadata_key_prefix || '' if pair_enumerable.is_a?(Hash) && pair_enumerable.each_key.all?{|k| k.start_with?(pfx)} @meta = pair_enumerable.dup else @meta = Hash.new pair_enumerable&.each{|(k,v)| key = (@meta_key_prefix + k) unless @meta_key_prefix.nil? || k.start_with?(@meta_key_prefix) @meta[key] = v } end nil end |
#size ⇒ Object
Note that duplications of the same key are counted toward this number
188 |
# File 'lib/nodepile/keyed_array.rb', line 188 def size = keys.length |
#to_a ⇒ Object
alias for #values
185 |
# File 'lib/nodepile/keyed_array.rb', line 185 def to_a = values() |
#to_h ⇒ Object
Note that if the same key name is duplicated multiple times, the leftmost value is used
124 125 126 127 128 129 |
# File 'lib/nodepile/keyed_array.rb', line 124 def to_h h = Hash.new # reverse order so that in the case of duplicates the leftmost dominates (-1..-@keys.length).step(-1).each{|i| h[@keys[i]] = @vals[i] } return h end |
#underlay(upper_kaa) ⇒ Object
316 |
# File 'lib/nodepile/keyed_array.rb', line 316 def underlay(upper_kaa) = self.dup.underlay!(upper_kaa) |
#underlay!(upper_kaa) ⇒ self
Given a KeyedArrayAccessor objects, update self to form a “merged” KeyedArrayAccessor where self “underlays” an “upper” array to generate a merged data structure. An overlay/underlay follows these rules: If “upper” and self have non-blank for the same element, then the upper element would “overlay” the corresponding entry in self. If the upper is blank, then it does not “overlay”.
Note that the object in array position zero is at the bottom of the overlay. Random Observation: If the upper_kaa is completely populated, the lower_kaa is essentially ignored.
NOTE: When the upper_kaa does not #conforms?(), the result
will have a key set containing the union of the upper and lower keysets.
Also, overlaying non-conforming objects will have worse performance.
Note that if it is possible, for the overlay to be generated without
adding keys, this strategy will be used.
Note: Metadata for self is unchanged by this method.
304 305 306 307 308 309 310 311 312 |
# File 'lib/nodepile/keyed_array.rb', line 304 def underlay!(upper_kaa) return self if self.equal?(upper_kaa) # return self (no-op) if conforms?(upper_kaa) upper_kaa._each_value_with_index(true){|upper_val,ix| @vals[ix] = upper_val} else upper_kaa.each_filled_pair(false){|key,upper_val| self[key] = upper_val} end return self end |
#update_metadata(key, value) ⇒ Object
87 88 89 90 91 |
# File 'lib/nodepile/keyed_array.rb', line 87 def (key,value) @meta = Hash.new if @meta.nil? k = key.start_with?(@meta_key_prefix) ? k : (@meta_key_prefix + key) @meta&.[]=(k,value) end |
#value_at(index) ⇒ Object
227 |
# File 'lib/nodepile/keyed_array.rb', line 227 def value_at(index) = @vals[index] |
#values ⇒ Object
182 |
# File 'lib/nodepile/keyed_array.rb', line 182 def values = return @vals.dup |