github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/ledger/complete/mtrie/flattener/iterator.go (about)

     1  package flattener
     2  
     3  import (
     4  	"github.com/onflow/flow-go/ledger"
     5  	"github.com/onflow/flow-go/ledger/complete/mtrie/node"
     6  )
     7  
     8  // NodeIterator is an iterator over the nodes in a trie.
     9  // It guarantees a DESCENDANTS-FIRST-RELATIONSHIP in the sequence of nodes it generates:
    10  //   - Consider the sequence of nodes, in the order they are generated by NodeIterator.
    11  //     Let `node[k]` denote the node with index `k` in this sequence.
    12  //   - Descendents-First-Relationship means that for any `node[k]`, all its descendents
    13  //     have indices strictly smaller than k in the iterator's sequence.
    14  //
    15  // The Descendents-First-Relationship has the following important property:
    16  // When re-building the Trie from the sequence of nodes, one can build the trie on the fly,
    17  // as for each node, the children have been previously encountered.
    18  type NodeIterator struct {
    19  	// NodeIterator internal implementation
    20  	// NodeIterator is initialized with an empty stack and the trie's root node assigned to
    21  	// unprocessedRoot. On the FIRST call of Next(), the NodeIterator will traverse the trie
    22  	// starting from the root in a depth-first search (DFS) order (prioritizing the left child
    23  	// over the right, when descending). It pushed the nodes it encounters on the stack,
    24  	// until it hits a leaf node (which then forms the head of the stack).
    25  	// On each subsequent call of Next(), the NodeIterator always pops the head of the stack.
    26  	// Let `n` be the node which was popped from the stack.
    27  	// If the `n` has a parent, denominated as `p`, the parent is now the head of the stack.
    28  	// Parent `p` can either have one or two children.
    29  	//   * If the parent `p` has only one child, there is no other child of `p` to enumerate.
    30  	//   * If the parent has two children:
    31  	//       - if `n` is the left child, we haven't searched through `p.RightChild()`
    32  	//         (as priority is given to the left child)
    33  	//         => we search p.RightChild() and push nodes in DFS manner on the stack
    34  	//            until we hit the first leaf node again
    35  	// By induction, it follows that the head of the stack always contains a node,
    36  	// whose descendents have already been recalled:
    37  	//   * after the initial call of Next(), the head of the stack is a leaf node, which has
    38  	//	   no children, it can be recalled without restriction.
    39  	//   * When popping node `n` from the stack, its parent `p` (if it exists) is now the
    40  	//     head of the stack.
    41  	//     - If `p` has only one child, this child must be `n`.
    42  	//       Therefore, by recalling `n`, we have recalled all ancestors of `p`.
    43  	//     - If `n` is the right child, we haven already searched through all of `p`
    44  	//       descendents (as the `p.LeftChild` must have been searched before)
    45  	//       Therefore, by recalling `n`, we have recalled all ancestors of `p`
    46  	// Hence, it follows that the head of the stack always satisfies the
    47  	// Descendents-First-Relationship. As we search the trie in DFS manner, each
    48  	// node of the trie is recalled (once). Hence, the algorithm iterates all
    49  	// nodes of the MTrie while guaranteeing Descendents-First-Relationship.
    50  
    51  	// unprocessedRoot contains the trie's root before the first call of Next().
    52  	// Thereafter, it is set to nil (which prevents repeated iteration through the trie).
    53  	// This has the advantage, that we gracefully handle tries whose root node is nil.
    54  	unprocessedRoot *node.Node
    55  	stack           []*node.Node
    56  	// visitedNodes are nodes that were visited and can be skipped during
    57  	// traversal through dig(). visitedNodes is used to optimize node traveral
    58  	// IN FOREST by skipping nodes in shared sub-tries after they are visited,
    59  	// because sub-tries are shared between tries (original MTrie before register updates
    60  	// and updated MTrie after register writes).
    61  	// NodeIterator only uses visitedNodes for read operation.
    62  	// No special handling is needed if visitedNodes is nil.
    63  	// WARNING: visitedNodes is not safe for concurrent use.
    64  	visitedNodes map[*node.Node]uint64
    65  }
    66  
    67  // NewNodeIterator returns a node NodeIterator, which iterates through all nodes
    68  // comprising the MTrie. The Iterator guarantees a DESCENDANTS-FIRST-RELATIONSHIP in
    69  // the sequence of nodes it generates:
    70  //   - Consider the sequence of nodes, in the order they are generated by NodeIterator.
    71  //     Let `node[k]` denote the node with index `k` in this sequence.
    72  //   - Descendents-First-Relationship means that for any `node[k]`, all its descendents
    73  //     have indices strictly smaller than k in the iterator's sequence.
    74  //
    75  // The Descendents-First-Relationship has the following important property:
    76  // When re-building the Trie from the sequence of nodes, one can build the trie on the fly,
    77  // as for each node, the children have been previously encountered.
    78  // NodeIterator created by NewNodeIterator is safe for concurrent use
    79  // because visitedNodes is always nil in this case.
    80  func NewNodeIterator(n *node.Node) *NodeIterator {
    81  	return NewUniqueNodeIterator(n, nil)
    82  }
    83  
    84  // NewUniqueNodeIterator returns a node NodeIterator, which iterates through all unique nodes
    85  // that weren't visited.  This should be used for forest node iteration to avoid repeatedly
    86  // traversing shared sub-tries.
    87  // The Iterator guarantees a DESCENDANTS-FIRST-RELATIONSHIP in the sequence of nodes it generates:
    88  //   - Consider the sequence of nodes, in the order they are generated by NodeIterator.
    89  //     Let `node[k]` denote the node with index `k` in this sequence.
    90  //   - Descendents-First-Relationship means that for any `node[k]`, all its descendents
    91  //     have indices strictly smaller than k in the iterator's sequence.
    92  //
    93  // The Descendents-First-Relationship has the following important property:
    94  // When re-building the Trie from the sequence of nodes, one can build the trie on the fly,
    95  // as for each node, the children have been previously encountered.
    96  // WARNING: visitedNodes is not safe for concurrent use.
    97  func NewUniqueNodeIterator(n *node.Node, visitedNodes map[*node.Node]uint64) *NodeIterator {
    98  	// For a Trie with height H (measured by number of edges), the longest possible path
    99  	// contains H+1 vertices.
   100  	stackSize := ledger.NodeMaxHeight + 1
   101  	i := &NodeIterator{
   102  		stack:        make([]*node.Node, 0, stackSize),
   103  		visitedNodes: visitedNodes,
   104  	}
   105  	i.unprocessedRoot = n
   106  	return i
   107  }
   108  
   109  // Next moves the cursor to the next node in order for Value method to return it.
   110  // It returns true if there is a next node to iterate, in which case the Value method will return the node.
   111  // It returns false if there is no more node to iterate, in which case the Value method will return nil.
   112  func (i *NodeIterator) Next() bool {
   113  	if i.unprocessedRoot != nil {
   114  		// initial call to Next() for a non-empty trie
   115  		i.dig(i.unprocessedRoot)
   116  		i.unprocessedRoot = nil
   117  		return len(i.stack) > 0
   118  	}
   119  
   120  	// the current head of the stack, `n`, has been recalled
   121  	// we now inspect n's parent and dig into the parent's right child, if necessary
   122  	n := i.pop()
   123  	if len(i.stack) > 0 {
   124  		// If there are more elements on the stack, the next element on the stack is n's parent `p`.
   125  		// Before we can recall `p`, we need to dig into the parent's right child, if we haven't
   126  		// done so already. As we decent into the left child with priority, the only case where
   127  		// we still need to dig into the right child is, if n is p's left child.
   128  		parent := i.peek()
   129  		if parent.LeftChild() == n {
   130  			i.dig(parent.RightChild())
   131  		}
   132  		return true
   133  	}
   134  	return false // as len(i.stack) == 0, i.e. there are no more elements to recall
   135  }
   136  
   137  // Value will return the current node at the cursor.
   138  // Note: you should call Next() before calling
   139  func (i *NodeIterator) Value() *node.Node {
   140  	if len(i.stack) == 0 {
   141  		return nil
   142  	}
   143  	return i.peek()
   144  }
   145  
   146  func (i *NodeIterator) pop() *node.Node {
   147  	if len(i.stack) == 0 {
   148  		return nil
   149  	}
   150  	headIdx := len(i.stack) - 1
   151  	head := i.stack[headIdx]
   152  	i.stack = i.stack[:headIdx]
   153  	return head
   154  }
   155  
   156  func (i *NodeIterator) peek() *node.Node {
   157  	return i.stack[len(i.stack)-1]
   158  }
   159  
   160  func (i *NodeIterator) dig(n *node.Node) {
   161  	if n == nil {
   162  		return
   163  	}
   164  	if _, found := i.visitedNodes[n]; found {
   165  		return
   166  	}
   167  	for {
   168  		i.stack = append(i.stack, n)
   169  		if lChild := n.LeftChild(); lChild != nil {
   170  			if _, found := i.visitedNodes[lChild]; !found {
   171  				n = lChild
   172  				continue
   173  			}
   174  		}
   175  		if rChild := n.RightChild(); rChild != nil {
   176  			if _, found := i.visitedNodes[rChild]; !found {
   177  				n = rChild
   178  				continue
   179  			}
   180  		}
   181  		return
   182  	}
   183  }