Module: Tins::DeepTransform
- Included in:
- HashSymbolizeKeysRecursive
- Defined in:
- lib/tins/deep_transform.rb
Overview
DeepTransform provides a robust engine for traversing an object tree and applying a transformation to each node iteratively, preventing stack overflows.
Instance Method Summary collapse
-
#_transform_iterative(root, key: nil, value: nil, circular:) ⇒ Object
private
The core iterative engine used to traverse and transform object trees.
-
#deep_transform(key: nil, value: nil, circular: nil) ⇒ Object
Transforms an object and its children using the provided lambdas.
Instance Method Details
#_transform_iterative(root, key: nil, value: nil, circular:) ⇒ Object (private)
This method relies on to_hash and to_ary to identify container
nodes. Objects must implement these methods if they are to be treated
as branch nodes in the tree.
The core iterative engine used to traverse and transform object trees.
This method implements a non-recursive, two-pass approach to avoid
SystemStackError on deeply nested structures:
- Discovery Pass: Performs a pre-order traversal using an explicit stack
to identify all reachable nodes in the tree, tracking them by their
object ID (
__id__) to handle shared references and circularities. - Reconstruction Pass: Processes the discovered nodes in reverse order (bottom-up). This ensures that child nodes are transformed and stored in the results map before their parent nodes are processed.
56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 |
# File 'lib/tins/deep_transform.rb', line 56 def _transform_iterative(root, key: nil, value: nil, circular:) key ||= -> x { x } value ||= -> x { x } # Pass 1: Discovery (Pre-order traversal to find all nodes) nodes = [] stack = [ [ nil, root, nil ] ] visited = {} while stack.any? idx, node, cont = stack.pop next if visited[node.__id__] visited[node.__id__] = true nodes << [ idx, node, cont ] # Unfold containers to find children for discovery if node.respond_to?(:to_hash) node.to_hash.each_pair { |k, v| stack << [ k, v, node ] } elsif node.respond_to?(:to_ary) node.to_ary.each_with_index { |v, i| stack << [ i, v, node ] } end end # Pass 2: Bottom-Up Reconstruction results = {} nodes.reverse_each do |idx, node, cont| transformed = case value.arity when 1 then value.(node) when 2 then value.(idx, node) when 3 then value.(idx, node, cont) else raise ArgumentError, 'value lambda has to be arity in 1..3' end if transformed.respond_to?(:to_hash) hash = transformed.to_hash new_hash = hash.dup.tap(&:clear) hash.each do |k, v| # Resolve child from results map or mark as circular new_hash[key.(k)] = results[v.__id__] || (v.respond_to?(:to_hash) || v.respond_to?(:to_ary) ? circular : v) end transformed = new_hash elsif transformed.respond_to?(:to_ary) ary = transformed.to_ary transformed = ary.map do |v| results[v.__id__] || (v.respond_to?(:to_hash) || v.respond_to?(:to_ary) ? circular : v) end end results[node.__id__] = transformed end results[root.__id__] end |
#deep_transform(key: nil, value: nil, circular: nil) ⇒ Object
Transforms an object and its children using the provided lambdas. This implementation uses an iterative bottom-up approach to avoid SystemStackError.
21 22 23 24 |
# File 'lib/tins/deep_transform.rb', line 21 def deep_transform(key: nil, value: nil, circular: nil) block_given? and raise ArgumentError, '&block not supported' _transform_iterative(self, key: key, value: value, circular: circular) end |