Module: Hm::Algo

Defined in:
lib/hm/algo.rb

Constant Summary collapse

NONDUPABLE =

JRuby, I am looking at you

[Symbol, Numeric, NilClass, TrueClass, FalseClass].freeze

Class Method Summary collapse

Class Method Details

.deep_copy(value) ⇒ Object



13
14
15
16
17
18
19
20
21
22
23
24
25
# File 'lib/hm/algo.rb', line 13

def deep_copy(value)
  # FIXME: ignores Struct/OpenStruct (which are diggable too)
  case value
  when Hash
    value.map { |key, val| [key, deep_copy(val)] }.to_h
  when Array
    value.map(&method(:deep_copy))
  when *NONDUPABLE
    value
  else
    value.dup
  end
end

.delete(collection, key) ⇒ Object



6
7
8
# File 'lib/hm/algo.rb', line 6

def delete(collection, key)
  collection.is_a?(Array) ? collection.delete_at(key) : collection.delete(key)
end

.nest_hashes(value, *keys) ⇒ Object



49
50
51
52
53
54
55
# File 'lib/hm/algo.rb', line 49

def nest_hashes(value, *keys)
  return value if keys.empty?

  key = keys.shift
  val = keys.empty? ? value : nest_hashes(value, *keys)
  key.is_a?(Integer) ? [].tap { |arr| arr[key] = val } : {key => val}
end

.robust_enumerator(collection) ⇒ Object

Enumerates through entire collection with “current key/current value” at each point, even if elements are deleted in a process of enumeration



29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/hm/algo.rb', line 29

def robust_enumerator(collection)
  case collection
  when Hash, Struct
    collection.each_pair.to_a
  when Array
    Enumerator.new do |y|
      cur = collection.size
      until cur.zero?
        pos = collection.size - cur
        y << [pos, collection[pos]]
        cur -= 1
      end
    end
  when ->(c) { c.respond_to?(:each_pair) }
    collection.each_pair.to_a
  else
    fail TypeError, "Can't dig/* in #{collection.class}"
  end
end

.visit(what, rest, path = [], not_found: ->(*) {}, &found) ⇒ Object



57
58
59
60
61
62
63
64
65
66
# File 'lib/hm/algo.rb', line 57

def visit(what, rest, path = [], not_found: ->(*) {}, &found)
  Dig.diggable?(what) or fail TypeError, "#{what.class} is not diggable"

  key, *rst = rest
  if key == WILDCARD
    visit_wildcard(what, rst, path, found: found, not_found: not_found)
  else
    visit_regular(what, key, rst, path, found: found, not_found: not_found)
  end
end

.visit_all(what, path = [], &block) ⇒ Object



68
69
70
71
72
73
# File 'lib/hm/algo.rb', line 68

def visit_all(what, path = [], &block)
  robust_enumerator(what).each do |key, val|
    yield(what, [*path, key], val)
    visit_all(val, [*path, key], &block) if Dig.diggable?(val)
  end
end

.visit_regular(what, key, rest, path, found:, not_found:) ⇒ Object

rubocop:disable Metrics/ParameterLists



84
85
86
87
88
89
90
91
92
# File 'lib/hm/algo.rb', line 84

def visit_regular(what, key, rest, path, found:, not_found:) # rubocop:disable Metrics/ParameterLists
  internal = Dig.dig(what, key)

  # NB: NotFound is signified by special value, because `nil` can still be legitimate value in hash
  return not_found.(what, [*path, key], rest) if internal == Dig::NotFound

  rest.empty? and return found.(what, [*path, key], internal)
  visit(internal, rest, [*path, key], not_found: not_found, &found)
end

.visit_wildcard(what, rest, path, found:, not_found:) ⇒ Object



75
76
77
78
79
80
81
82
# File 'lib/hm/algo.rb', line 75

def visit_wildcard(what, rest, path, found:, not_found:)
  iterator = robust_enumerator(what)
  if rest.empty?
    iterator.map { |key, val| found.(what, [*path, key], val) }
  else
    iterator.map { |key, el| visit(el, rest, [*path, key], not_found: not_found, &found) }
  end
end