Module: DeepEnumerable

Included in:
Array, Hash
Defined in:
lib/deep_enumerable.rb

Overview

A set of general methods that can be applied to any conformant nested structure

Instance Method Summary collapse

Instance Method Details

#deep_diff(other, &block) ⇒ Object

Subtracts the leaves of one DeepEnumerable from another.

Examples:

>> alice = {name: "alice", age: 26}
>> bob   = {name: "bob",   age: 26}
>> alice.deep_diff(bob)
=> {:name=>"alice"}

>> bob   = {friends: ["alice","carol"]}
>> carol = {friends: ["alice","bob"]}
>> bob.deep_diff(carol)
=> {:friends=>"carol"}

Returns:

  • a result of the same structure as the primary DeepEnumerable.



20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# File 'lib/deep_enumerable.rb', line 20

def deep_diff(other, &block)
  shallow_keys.each_with_object(empty) do |key, res|
    s_val = (self[key] rescue nil) #TODO don't rely on rescue
    o_val = (other[key] rescue nil)

    comparator = block || :==.to_proc

    if s_val.respond_to?(:deep_diff) && o_val.respond_to?(:deep_diff)
      diff = s_val.deep_diff(o_val, &block)
      res[key] = diff if diff.any?
    elsif !comparator.call(s_val, o_val)
      res[key] = s_val
    end
  end
end

#deep_diff_symmetric(other, &block) ⇒ Object Also known as: deep_outersect

Computes the complement of the intersection of two DeepEnumerables.

Examples:

>> alice = {:name=>"alice", :age=>26}
>> bob   = {:name=>"bob",   :age=>26}
>> alice.deep_diff_symmetric(bob)
=> {:name=>["alice", "bob"]}

>> bob   = {:friends=>["alice","carol"]}
>> carol = {:friends=>["alice","bob"]}
>> bob.deep_diff_symmetric(carol)
=> {:friends=>{1=>["carol", "bob"]}}

Returns:

  • The common structure of both arguments, with tuples containing differing values in the leaf nodes.



52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# File 'lib/deep_enumerable.rb', line 52

def deep_diff_symmetric(other, &block)
  (shallow_keys + other.shallow_keys).each_with_object({}) do |key, res|
    s_val = (self[key] rescue nil) #TODO don't rely on rescue
    o_val = (other[key] rescue nil)

    comparator = block || :==.to_proc

    if s_val.respond_to?(:deep_diff_symmetric) && o_val.respond_to?(:deep_diff_symmetric)
      diff = s_val.deep_diff_symmetric(o_val, &block)
      res[key] = diff if diff.any?
    elsif !comparator.call(s_val, o_val)
      res[key] = [s_val, o_val]
    end
  end
end

#deep_dupObject

Deeply copy a DeepEnumerable

Returns:

  • the same data structure at a different memory address



73
74
75
# File 'lib/deep_enumerable.rb', line 73

def deep_dup
  deep_select{true}
end

#deep_each(&block) ⇒ Object

Iterate elements of a DeepEnumerable

Examples:

>> {event: {id: 1, title: 'bowling'}}.deep_each.to_a
=> [[{:event=>:id}, 1], [{:event=>:title}, "bowling"]]

>> [:a, [:b, :c]].deep_each.to_a
=> [[0, :a], [{1=>0}, :b], [{1=>1}, :c]]

>> {events: [{title: 'movie'}, {title: 'dinner'}]}.deep_each.to_a
=> [[{:events=>{0=>:title}}, "movie"], [{:events=>{1=>:title}}, "dinner"]]

Returns:

  • an iterator over each deep-key/value pair for every leaf



91
92
93
# File 'lib/deep_enumerable.rb', line 91

def deep_each(&block)
  depth_first_map.each(&block)
end

#deep_flat_map(&block) ⇒ Object

Concatenate all the results from the supplied code block together.

Examples:

>> {a: {b: 1, c: {d: 2, e: 3}, f: 4}, g: 5}.deep_flat_map{|k,v| v*2}
=> [2, 4, 6, 8, 10]

>> {a: {b: 1, c: {d: 2, e: 3}, f: 4}, g: 5}.deep_flat_map{|k,v| [v, v*2]}
=> [1, 2, 2, 4, 3, 6, 4, 8, 5, 10]

Returns:

  • an array with the results of running block once for every leaf element in the original structure, all flattened together.



106
107
108
# File 'lib/deep_enumerable.rb', line 106

def deep_flat_map(&block)
  deep_each.flat_map(&block)
end

#deep_get(key) ⇒ Object

Retrieve a nested element from a DeepEnumerable

Examples:


>> prefix_tree = {"a"=>{"a"=>"aardvark", "b"=>["abacus", "abadon"], "c"=>"actuary"}}

>> prefix_tree.deep_get("a")
=> {"a"=>"aardvark", "b"=>["abacus", "abadon"], "c"=>"actuary"}

>> prefix_tree.deep_get("a"=>"b")
=> ["abacus", "abadon"]

Returns:

  • a DeepEnumerable representing the subtree specified by the query key



125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/deep_enumerable.rb', line 125

def deep_get(key)
  if DeepEnumerable::nested_key?(key)
    key_head, key_tail = DeepEnumerable::split_key(key)
    if self[key_head].respond_to?(:deep_get)
      self[key_head].deep_get(key_tail)
    else
      nil #SHOULD? raise an error
    end
  else
    self[key]
  end
end

#deep_inject(initial, &block) ⇒ Object

Fold over all leaf nodes

Examples:

>> friends = [{name: 'alice', age: 26}, {name: 'bob', age: 26}]
>> friends.deep_inject(Hash.new{[]}) {|sum, (k, v)| sum[k.values.first] <<= v; sum}
=> {:name=>["alice", "bob"], :age=>[26, 26]}

Returns:

  • The accumulation of the results of executing the provided block over every element in the DeepEnumerable



147
148
149
# File 'lib/deep_enumerable.rb', line 147

def deep_inject(initial, &block)
  deep_each.inject(initial, &block)
end

#deep_intersect(other, &block) ⇒ Object

Describes the similarities between two DeepEnumerables.

Examples:

>> alice = {:name=>"alice", :age=>26}
>> bob   = {:name=>"bob",   :age=>26}
>> alice.deep_intersect(bob)
=> {:age=>26}

>> bob   = {:friends=>["alice","carol"]}
>> carol = {:friends=>["alice","bob"]}
>> bob.deep_intersect(carol)
=> {:friends=>["alice"]}

Returns:

  • a result of the same structure as the primary DeepEnumerable.



167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
# File 'lib/deep_enumerable.rb', line 167

def deep_intersect(other, &block)
  (shallow_keys + other.shallow_keys).each_with_object(empty) do |key, res|
    s_val = (self[key] rescue nil) #TODO don't rely on rescue
    o_val = (other[key] rescue nil)

    comparator = block || :==.to_proc

    if s_val.respond_to?(:deep_intersect) && o_val.respond_to?(:deep_intersect)
      int = s_val.deep_intersect(o_val, &block)
      res[key] = int if int.any?
    elsif comparator.call(s_val, o_val)
      res[key] = s_val
    end
  end
end

#deep_map(&block) ⇒ Object

Create a new nested structure populated by the result of executing block on the deep-keys and values of the original DeepEnumerable

Examples:

>> {a: [1, 2]}.deep_map{|k, v| [k, v]}
=> {:a=>[[{:a=>0}, 1], [{:a=>1}, 2]]}

Returns:

  • A copy of the input, updated by the result of the block



210
211
212
# File 'lib/deep_enumerable.rb', line 210

def deep_map(&block)
  deep_dup.deep_map!(&block)
end

#deep_map!(&block) ⇒ Object

Returns the result of running block on each leaf of this DeepEnumerable

Examples:

>> h = {a: [1, 2]}
>> h.deep_map!{|k, v| [k, v]}
>> h
=> {:a=>[[{:a=>0}, 1], [{:a=>1}, 2]]}

Returns:

  • The original structure updated by the result of the block



193
194
195
196
197
198
199
200
# File 'lib/deep_enumerable.rb', line 193

def deep_map!(&block)
  if block_given?
    deep_each{|k,v| deep_set(k, block.call([k, v]))}
    self
  else
    deep_each
  end
end

#deep_map_values(&block) ⇒ Object

Creates a new nested structure populated by the result of executing block on the values of the original DeepEnumerable

Examples:

>> {a: [1, 2].deep_map_values{v| v*2}
=> {:a=>[2, 4]}

Returns:

  • A copy of the input, updated by the result of the block



236
237
238
# File 'lib/deep_enumerable.rb', line 236

def deep_map_values(&block)
  deep_dup.deep_map_values!(&block)
end

#deep_map_values!(&block) ⇒ Object

Modifies this collection to use the result of block as the values

Examples:

>> h = {a: [1, 2]}
>> h.deep_map_values!{v| v*2}
>> h
=> {:a=>[2, 4]}

Returns:

  • The original structure updated by the result of the block



224
225
226
# File 'lib/deep_enumerable.rb', line 224

def deep_map_values!(&block)
  deep_map!{|_, v| block.call(v)}
end

#deep_reject(&block) ⇒ Object

Filter leaf nodes by the result of the given block

Examples:

>> inventory = {fruit: {apples: 4, oranges: 7}}

>> inventory.deep_reject{|k, v| v > 5}
=> {:fruit=>{:apples=>4}}

>> inventory.deep_reject(&:even?)
=> {:fruit=>{:oranges=>7}}

Returns:

  • a copy of the input, filtered by the given predicate



254
255
256
257
258
259
260
261
# File 'lib/deep_enumerable.rb', line 254

def deep_reject(&block)
  new_block =
    case block.arity
    when 2 then ->(k,v){!block.call(k, v)}
    else        ->(v){  !block.call(v)}
    end
  deep_select(&new_block)
end

#deep_select(&block) ⇒ Object

Filter leaf nodes by the result of the given block

Examples:

>> inventory = {fruit: {apples: 4, oranges: 7}}

>> inventory.deep_select{|k, v| v > 5}
=> {:fruit=>{:oranges=>7}}

>> inventory.deep_select(&:even?)
=> {:fruit=>{:apples=>4}}

Returns:

  • a copy of the input, filtered by the given predicate



276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
# File 'lib/deep_enumerable.rb', line 276

def deep_select(&block)
  copy = self.select{false} # get an empty version of this shallow collection

  # insert/push a selected item into the copied enumerable
  accept = lambda do |k, v|
    # Don't insert elements at arbitrary positions in an array if appending is an option
    if copy.respond_to?('push') # jruby has a Hash#<< method
      copy.push(v)
    else
      copy[k] = v
    end
  end

  shallow_each do |k, v|
    if v.respond_to?(:deep_select)
      selected = v.deep_select(&block)
      accept.call(k, selected)
    else
      res =
        case block.arity
        when 2 then block.call(k, v)
        else    block.call(v)
        end

      if res
        accept.call(k, (v.dup rescue v)) # FixNum's and Symbol's can't/shouldn't be dup'd
      end
    end
  end
  
  copy
end

#deep_set(key, val) ⇒ tentative

Update a DeepEnumerable, indexed by an Array or Hash.

Intermediate values are created when necessary, with the same type as its parent.

Examples:

>> [].deep_set({1 => 2}, 3)
=> [nil, [nil, nil, 3]]
>> {}.deep_set([1, 2, 3], 4)
=> {1=>{2=>{3=>4}}}

Returns:

  • (tentative)

    returns the object that’s been modified. Warning: This behavior is subject to change.



322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
# File 'lib/deep_enumerable.rb', line 322

def deep_set(key, val)
  if DeepEnumerable::nested_key?(key)
    key_head, key_tail = DeepEnumerable::split_key(key)

    if key_tail.nil?
      self[key_head] = val
    else
      if self[key_head].respond_to?(:deep_set)
        self[key_head].deep_set(key_tail, val)
      else
        self[key_head] = empty.deep_set(key_tail, val)
      end
    end
  elsif !key.nil? # don't index on nil
    self[key] = val
  end

  self #SHOULD? return val instead of self
end

#deep_valuesObject

List the values stored at every leaf

Examples:

>> prefix_tree = {"a"=>{"a"=>"aardvark", "b"=>["abacus", "abadon"], "c"=>"actuary"}}
>> prefix_tree.deep_values
=> ["aardvark", "abacus", "abadon", "actuary"]

Returns:

  • a list of every leaf value



351
352
353
# File 'lib/deep_enumerable.rb', line 351

def deep_values
  deep_flat_map{|_, v| v}
end

#deep_zip(other) ⇒ Object

Combine two DeepEnumerables into one, with the elements from each joined into tuples

Examples:

>> inventory = {fruit: {apples: 4,    oranges: 7}}
>> prices    = {fruit: {apples: 0.79, oranges: 1.21}}
>> inventory.deep_zip(prices)
=> {:fruit=>{:apples=>[4, 0.79], :oranges=>[7, 1.21]}}

Returns:

  • one data structure with elements from both arguments joined together



366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
# File 'lib/deep_enumerable.rb', line 366

def deep_zip(other)
  (shallow_keys).inject(empty) do |res, key|
    s_val = self[key]
    o_val = (other[key] rescue nil) #TODO don't rely on rescue

    comparator = :==.to_proc

    if s_val.respond_to?(:deep_zip) && o_val.respond_to?(:deep_zip)
      diff = s_val.deep_zip(o_val)
      diff.empty? ? res : res.deep_set(key, diff)
    else
      res.deep_set(key, [s_val, o_val])
    end
  end
end

#emptyObject

A copy of the DeepEnumerable containing no elements

Examples:

>> inventory = {fruit: {apples: 4, oranges: 7}}
>> inventory.empty
=> {}

Returns:

  • a new object of the same type as the original collection, only empty



392
393
394
# File 'lib/deep_enumerable.rb', line 392

def empty
  select{false}
end

#shallow_each(&block) ⇒ Object

The primary iterator of a DeepEnumerable If this method is implemented DeepEnumerable can construct every other method in terms of shallow_each.



454
455
456
# File 'lib/deep_enumerable.rb', line 454

def shallow_each(&block)
  shallow_key_value_pairs.each(&block)
end

#shallow_key_value_pairsObject

Provide a homogenous |k,v| iterator for Arrays/Hashes/DeepEnumerables TODO test this



398
399
400
# File 'lib/deep_enumerable.rb', line 398

def shallow_key_value_pairs
  shallow_keys.map{|k| [k, self[k]]}
end

#shallow_map_keys(&block) ⇒ Object

Returns a new collection where every top-level element is replaced with the result of the given block



426
427
428
# File 'lib/deep_enumerable.rb', line 426

def shallow_map_keys(&block)
  deep_dup.shallow_map_keys!(&block)
end

#shallow_map_keys!(&block) ⇒ Object

Replaces every top-level element with the result of the given block



404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
# File 'lib/deep_enumerable.rb', line 404

def shallow_map_keys!(&block)
  new_kvs = shallow_key_value_pairs.map do |k, v|
    new_key = 
      if block.arity == 2
        block.call(k, v)
      else
        block.call(k)
      end

    self.delete(k) #TODO This is not defined on Enumerable!
    [new_key, v]
  end

  new_kvs.each do |k, v|
    self[k] = v
  end

  self
end

#shallow_map_values(&block) ⇒ Object

Returns a new collection where every top-level element is replaced with the result of the given block



447
448
449
# File 'lib/deep_enumerable.rb', line 447

def shallow_map_values(&block)
  deep_dup.shallow_map_values!(&block)
end

#shallow_map_values!(&block) ⇒ Object

Replaces every top-level element with the result of the given block



432
433
434
435
436
437
438
439
440
441
442
443
# File 'lib/deep_enumerable.rb', line 432

def shallow_map_values!(&block)
  shallow_key_value_pairs.each do |k, v|
      self[k] = 
        if block.arity == 2
          block.call(k, v)
        else
          block.call(v)
        end
  end

  self
end