Class: Hash

Inherits:
Object show all
Defined in:
lib/epitools/core_ext/hash.rb

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.lazy!Object

Hash keys become methods, kinda like OpenStruct. These methods have the lowest priority, so be careful. They will be overridden by any methods on Hash.


190
191
192
193
194
195
196
197
198
199
200
# File 'lib/epitools/core_ext/hash.rb', line 190

def self.lazy!
  Hash.class_eval do
    def method_missing(name, *args)
      if args.any?
        super
      else
        self[name] || self[name.to_s]
      end
    end
  end
end

.of_arraysObject

Returns a new Hash whose values default to empty arrays. (Good for collecting things!)

eg:

Hash.of_arrays[:yays] << "YAY!"

145
146
147
# File 'lib/epitools/core_ext/hash.rb', line 145

def self.of_arrays
  new {|h,k| h[k] = [] }
end

.of_integersObject

Returns a new Hash whose values default to 0. (Good for counting things!)

eg:

Hash.of_integers[:yays] += 1

165
166
167
# File 'lib/epitools/core_ext/hash.rb', line 165

def self.of_integers
  new(0)
end

.of_setsObject

Returns a new Hash whose values default to empty sets. (Good for collecting unique things!)

eg:

Hash.of_sets[:yays] << "Yay!"

155
156
157
# File 'lib/epitools/core_ext/hash.rb', line 155

def self.of_sets
  new {|h,k| h[k] = Set.new }
end

.of_unique_idsObject

Returns a new Hash which automatically assigns each unique key to an increasing counter.

eg:

> h = Hash.of_unique_ids
=> {}
> h["Person"]               #=> 0
> h["Another Person"]       #=> 1
> h["Yet Another Person"]   #=> 2
> h["Person"]               #=> 0
> h
=> {"Person"=>0, "Another Person"=>1, "Yet Another Person"=>2}

182
183
184
# File 'lib/epitools/core_ext/hash.rb', line 182

def self.of_unique_ids
  new { |h,k| h[k] = h.size }
end

Instance Method Details

#+(other) ⇒ Object

`hash1 + hash2` merge the two hashes, returning a new hash


7
8
9
# File 'lib/epitools/core_ext/hash.rb', line 7

def +(other)
  merge(other)
end

#-(other) ⇒ Object

`hash1 - hash2` removes keys from hash1 that exist in hash2, returning a new hash


14
15
16
# File 'lib/epitools/core_ext/hash.rb', line 14

def -(other)
  dup.delete_if { |k,v| other.includes?(k) }
end

#apply_diff(changes) ⇒ Object

Applies a Hash#diff changeset and returns the transformed hash.


353
354
355
# File 'lib/epitools/core_ext/hash.rb', line 353

def apply_diff(changes)
  deep_dup.apply_diff!(changes)
end

#apply_diff!(changes) ⇒ Object

Applies a Hash#diff changeset to this hash.


327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
# File 'lib/epitools/core_ext/hash.rb', line 327

def apply_diff!(changes)
  path = [[self, changes]]
  pos, local_changes = path.pop

  while local_changes
    local_changes.each_pair do |key, change|
      if change.kind_of?(Array)
        if change[1].nil?
          pos.delete key
        else
          pos[key] = change[1]
        end
      else
        path.push([pos[key], change])
      end
    end

    pos, local_changes = path.pop
  end

  self
end

#blank?Boolean

'true' if the Hash has no entries

Returns:

  • (Boolean)

21
22
23
# File 'lib/epitools/core_ext/hash.rb', line 21

def blank?
  not any?
end

#deep_dupObject

Duplicate this hash, including hashes nested inside of it.


360
361
362
363
364
365
366
367
# File 'lib/epitools/core_ext/hash.rb', line 360

def deep_dup
  duplicate = self.dup
  duplicate.each_pair do |k,v|
    tv = duplicate[k]
    duplicate[k] = tv.is_a?(Hash) && v.is_a?(Hash) ? tv.deep_dup : v
  end
  duplicate
end

#diff(other) ⇒ Object

Return all the changes necessary to transform `self` into `other`. (Works on nested hashes.) The result is a hash of => [old value, new value] pairs.

(NOTE: Since “nil” is used to denote a value was removed, you can't use this method to diff hashes where a value is “nil”.)


311
312
313
314
315
316
317
318
319
320
321
322
# File 'lib/epitools/core_ext/hash.rb', line 311

def diff(other)
  (self.keys + other.keys).uniq.inject({}) do |memo, key|
    unless self[key] == other[key]
      if self[key].kind_of?(Hash) && other[key].kind_of?(Hash)
        memo[key] = self[key].diff(other[key])
      else
        memo[key] = [self[key], other[key]]
      end
    end
    memo
  end
end

#map_keys(&block) ⇒ Object Also known as: transform_keys

Transforms the keys of the hash by passing them into the supplied block, and then using the blocks result as the new key.


76
77
78
# File 'lib/epitools/core_ext/hash.rb', line 76

def map_keys(&block)
  dup.map_keys!(&block)
end

#map_keys!(&block) ⇒ Object Also known as: transform_keys!

Runs map_keys on self.


63
64
65
66
67
68
69
# File 'lib/epitools/core_ext/hash.rb', line 63

def map_keys!(&block)
  keys.each do |key|
    value = delete(key)
    self[yield(key)] = value
  end
  self
end

#map_values(&block) ⇒ Object

Transforms the values of the hash by passing them into the supplied block, and then using the block's result as the new value.


56
57
58
# File 'lib/epitools/core_ext/hash.rb', line 56

def map_values(&block)
  dup.map_values!(&block)
end

#map_values!(&block) ⇒ Object

Runs map_values on self.


44
45
46
47
48
49
50
# File 'lib/epitools/core_ext/hash.rb', line 44

def map_values!(&block)
  keys.each do |key|
    value = self[key]
    self[key] = yield(value)
  end
  self
end

#mkdir_p(path) ⇒ Object

Makes each element in the `path` array point to a hash containing the next element in the `path`. Useful for turning a bunch of strings (paths, module names, etc.) into a tree.

Example:

h = {}
h.mkdir_p(["a", "b", "c"])    #=> {"a"=>{"b"=>{"c"=>{}}}}
h.mkdir_p(["a", "b", "whoa"]) #=> {"a"=>{"b"=>{"c"=>{}, "whoa"=>{}}}}

217
218
219
220
221
222
223
# File 'lib/epitools/core_ext/hash.rb', line 217

def mkdir_p(path)
  return if path.empty?
  dir = path.first
  self[dir] ||= {}
  self[dir].mkdir_p(path[1..-1])
  self
end

Print the result of `tree`


241
242
243
244
245
246
247
248
# File 'lib/epitools/core_ext/hash.rb', line 241

def print_tree(level=0, indent="  ", &block)
  dent = indent * level

  each do |key, val|
    puts block_given? ? yield(key, level) : "#{dent}#{key}"
    val.print_tree(level+1, indent, &block) if val.any?
  end
end

#query(template) ⇒ Object Also known as: mql

Query a hash using MQL (see: wiki.freebase.com/wiki/MQL_operators for reference)

Examples:

> query(name: /steve/)
> query(/title/ => ??)
> query(articles: [{title: ??}])
> query(responses: [])
> query("date_of_birth<" => "2000")

289
290
291
292
293
294
295
296
297
298
299
300
301
302
# File 'lib/epitools/core_ext/hash.rb', line 289

def query(template)
  results = []
  template.each do |key,val|
    case key
    when Regexp, String
    when Array
    when Hash
      results += hash.query(template)
    end
  end

  map do |key,val|
  end
end

#remove_blank_valuesObject

Returns a new Hash where blank values have been removed. (It checks if the value is blank by calling #blank? on it)


37
38
39
# File 'lib/epitools/core_ext/hash.rb', line 37

def remove_blank_values
  dup.remove_blank_values!
end

#remove_blank_values!Object

Runs “remove_blank_values” on self.


28
29
30
31
# File 'lib/epitools/core_ext/hash.rb', line 28

def remove_blank_values!
  delete_if{|k,v| v.blank?}
  self
end

#slice(*keys) ⇒ Object

Returns a hash containing only the keys passed as arguments.


125
126
127
# File 'lib/epitools/core_ext/hash.rb', line 125

def slice(*keys)
  dup.slice!(*keys)
end

#slice!(*keys) ⇒ Object

Alters the hash so it contains only the keys passed as arguments.


133
134
135
136
137
# File 'lib/epitools/core_ext/hash.rb', line 133

def slice!(*keys)
  keys = Set.new keys
  delete_if { |k,v| not keys.include? k }
  self
end

#symbolize_keysObject

Return a hash with its keys converted to symbols, for great justice


118
119
120
# File 'lib/epitools/core_ext/hash.rb', line 118

def symbolize_keys
  dup.symbolize_keys!
end

#symbolize_keys!Object

Convert the keys to symbols in-place, for fun and profit


111
112
113
# File 'lib/epitools/core_ext/hash.rb', line 111

def symbolize_keys!
  map_keys! { |k| k.to_sym }
end

#to_queryObject

Convert the hash into a GET query.


253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
# File 'lib/epitools/core_ext/hash.rb', line 253

def to_query
  params = ''
  stack = []

  each do |k, v|
    if v.is_a?(Hash)
      stack << [k,v]
    else
      params << "#{k}=#{v}&"
    end
  end

  stack.each do |parent, hash|
    hash.each do |k, v|
      if v.is_a?(Hash)
        stack << ["#{parent}[#{k}]", v]
      else
        params << "#{parent}[#{k}]=#{v}&"
      end
    end
  end

  params.chop! # trailing &
  params
end

#translate_keys(mapping) ⇒ Object

Same as `translate_keys!`, except it returns a copy of the hash


104
105
106
# File 'lib/epitools/core_ext/hash.rb', line 104

def translate_keys(mapping)
  dup.translate_keys!(mapping)
end

#translate_keys!(mapping) ⇒ Object

Translate keys to other keys, given a hash of old-key to new-key mappings.

eg: hash.translate_keys!(

  "Extreme Sports!!!!" => :extreme_sports,
  "Mediocre sports"    => :sports,
  "Pretty okay sports" => :sports,
  "Golf"               => :liesure_activities,
)

91
92
93
94
95
96
97
98
99
# File 'lib/epitools/core_ext/hash.rb', line 91

def translate_keys!(mapping)
  # TODO: Allow regexes and lambdas (eg: translate_keys!(/.+/ => ->(key) { key.to_sym })
  mapping.each do |src,dest|
    if includes? src
      self[dest] = delete(src)
    end
  end
  self
end

#tree(level = 0, indent = " ") ⇒ Object

Turn some nested hashes into a tree (returns an array of strings, padded on the left with indents.)


228
229
230
231
232
233
234
235
236
# File 'lib/epitools/core_ext/hash.rb', line 228

def tree(level=0, indent="  ")
  result = []
  dent = indent * level
  each do |key, val|
    result << dent+key.to_s
    result += val.tree(level+1) if val.any?
  end
  result
end