text: In the previous post, I have explained two basic helper classes
  LTMData and
  NData and gave you an example of a picture showing content of Leo model data for one example outline. In this part, I will use more pictures like that one. If you don’t understand what the picture represents, please read again previous post.
  Let’s start with one of the basic operations:
  delete_node(pos)
  How to delete node
  To delete node (and of course its entire subtree) from new data model, we have to delete items from all lists:
  positions, nodes, levels. We also need to update size of all ancestor nodes. We have to cut links between its parent node and this one. We need to delete this block of items from all other occurrences of the parent node in the outline. And finally, we may have to clean
  attrs if the deleted node occurred only once in the outline.
  Here is a picture that shows changes in model data when
  delete_node(P5) is executed.
  As you can see there are not many differences (which are marked with red colour). In first four columns the only differences are sizes of ancestor nodes. In the right part we can see that node at the position P2 has changed his list of parents. Missing parent is actually deleted node. Also node at the position P4 has changed list of its children. Missing child is the deleted node. And of course some rows are deleted.
  Now let’s see the code that has done all these changes to the model.
  def delete_node(self, pos): ( positions, nodes, attrs, levels, expanded, marked) = self.data # this node i = positions.index(pos) gnx = nodes[i] sz = attrs[gnx].size ns = nodes[i:i+sz] if sz > len(positions) - 2: # cant delete the only top level node return # parent node pi = parent_index(levels, i) pp = positions[pi] pgnx = nodes[pi] psz = attrs[pgnx].size # distance from parent pdist = i - pi # remove all nodes in subtree for pxi in gnx_iter(nodes, pgnx, psz-sz): a = pxi + pdist b = a + sz del nodes[a:b] del positions[a:b] del levels[a:b] # now reduce sizes of all ancestors update_size(attrs, pgnx, -sz) attrs[gnx].parents.remove(pgnx) self._update_children(pgnx) clean_parents(attrs, nodes, ns)
  Is deletion allowed
  If the outline has a single top level node its size must be one less than the number of positions in the outline. That is why we check this condition in lines 10-12 and return immediately if that is the case. We don’t allow the only top-level node to be deleted.
  Deleting items
  In line 21 we calculate the distance between the node being deleted and its parent node. In our case we are deleting the node at the position P5, and its parent is at position P4, so the distance
  pdist = 1. That means wherever we find parent node in the outline, we are certain that at the index increased by
  pdist we will have clone of the node we are deleting.
  Function
  gnx_iter is a generator that yields indexes of all occurrences of the given gnx (in our case we are iterating occurrences of our parent node). That is why in line 24 we use
  pgnx as argument for
  gnx_iter. Here is the definition of the generator:
  def gnx_iter(nodes, gnx, sz=1): i = 0 try: while i < len(nodes): i = nodes.index(gnx, i) yield i i += sz except ValueError: pass
  As you can see it uses
  sz argument as a hint for how many nodes it is safe to skip before searching for another occurrence. Normally, node can’t be in its own subtree so it is safe to skip its size. However, when we are deleting from this node we are actually reducing its size by the number of deleted nodes. That is why in the line 24 we subtract size of this node from the size of its parent as third argument to the
  gnx_iter generator.
  Inside for loop (lines 24-29),
  pxi is index of parent node. At the distance of
  pdist begins data block that represents the node we are deleting. The block is
  sz items long. In lines 27-29 we delete the block from all lists.
  Updating size
  The function
  update_size recursively updates sizes of all ancetors.
  def update_size(attrs, gnx, delta): def ds(x): attrs[x].size += delta for x1 in attrs[x].parents: ds(x1) ds(gnx)
  Cutting links between nodes
  Line 34 removes link from this node to its parent. Method
  _update_children, recalculates list of children for a given node.
  def _update_children(self, pgnx): (positions, nodes, attrs, levels, expanded, marked) = self.data i = nodes.index(pgnx) chn = [] B = i + attrs[pgnx].size j = i + 1 while j < B: cgnx = nodes[j] chn.append(cgnx) j += attrs[cgnx].size attrs[pgnx].children = chn
  The first child if there is one, has index one greater than the index of its parent. That is why we start with the index
  i + 1 in line 58. Upper limit
  B is size of the parent away from parent index (line 57). We are increasing index
  j by the size of child (line 62). And finally in line 63 we update list of children.
  Cleaning lists of parents
  There is one more thing that needs to be done. There is a possiblity that any of deleted nodes was parent to some node in the remaining part of the outline. In our example we can see that node at position P20 has parents with the gnxes
  [B, F, H, N]. Node that we are deleting has gnx
  B. As you can see, after deletion node in position P20 needs to update its list of parents by removing any gnx that was in the deleted block. After deletion list of its parents is
  [F, H, N]. In line 36 we call function for cleaning lists of parents. Here is its definition:
  def clean_parents(attrs, nodes, ns): missing = set(ns) - set(nodes) for x in ns: ps = attrs[x].parents ps[:] = [x1 for x1 in ps if x1 not in missing] missing = set(attrs) - set(nodes) for x in missing: attrs.pop(x)
  It performs two cleanings: first in lines 65-68 and second cleaning lines 69-71. First we calculate all gnxes of all nodes that are not anymore part of the outline line 65. Argument
  ns is a list of nodes that were removed from the outline. If some of the removed nodes had some clone outside the deleted block, then this node should not be cleaned from lists of parents. If however there were no other clones outside deleted block, then such nodes should be removed from all lists of parents (line 68).
  In the second cleaning we are calculating which gnxes are still in
  attrs but are not anymore in the outline (line 69). For each missing node we remove it from the
  attrs dictionary. This might be unnecessary but makes everything cleaner and at least makes testing easier. Because of this we are sure that every item in
  attrs is part of the outline.
  In the following part you can find description of the other tree operations.