That sounds like a bug/limitation in @sschwarzer's algorithm to me, but maybe 
he can verify. Good catch, @e.

Another pretty different approach that I use in `cligen/tern.nim` (that does 
not support in-iteration deletion) is something like this: 
    
    
    const NUL* = '\0'
    type
      NodeOb*[T] {.acyclic.} = object
        ch*: char
        cnt*: int
        when T isnot void: val*: T
        kid*: array[3, ref NodeOb[T]]   #0,1,2 ~ <,=,>
      Node*[T] = ref NodeOb[T]
      Tern*[T] = object   ## A Tern can be used as either a mapping from strings
        root*: Node[T]    ## to type ``T`` or as a set(strings) if ``T`` is 
void.
        count: int        ## Number of elements
        depth: int        ## Depth of Tree
    
    iterator leaves[T](r: Node[T], depth:int, pfx=""): tuple[k:string, 
n:Node[T]] =
      type                                               #Nim iterators should 
grow
        Which = enum st0, st1, st2, stR                  #..recursion 
capability so
        State = tuple[st: Which, k: string, n: Node[T]]  #..this headache can 
be as
      if r != nil:                                       #..easy as `print`.
        var stack = newSeqOfCap[State](depth)
        stack.add (st: st0, k: pfx, n: r)
        while stack.len > 0:
          let state = stack[^1].addr
          if state.n == nil: break
          case state.st
          of st0:
            state.st = st1
            if state.n.kid[0] != nil:
              stack.add (st: st0, k: state.k, n: state.n.kid[0])
          of st1:
            state.st = st2
            if state.n.ch == NUL:
              yield (k: state.k, n: state.n)
            elif state.n.kid[1] != nil:
              stack.add (st: st0, k: state.k & $state.n.ch, n: state.n.kid[1])
          of st2:
            state.st = stR
            if state.n.kid[2] != nil:
              stack.add (st: st0, k: state.k, n: state.n.kid[2])
          of stR: discard stack.pop
    
    
    Run

The above is probably pretty close to what compilers generate for recursive 
function calls. Of course, as mentioned I think things like this are more 
elegant/easy to understand: 
    
    
    proc print*[T](p: Node[T], depth=0) = #2,1,0 gives std emoji-orientation
      if p == nil: return                 #..i.e. ":-)" head-tilt not "(-:".
      print(p.kid[2], depth + 1)
      echo strutils.repeat("  ", depth), cast[int](p), " ch: ", p.ch.repr, " 
cnt: ", p.cnt
      print(p.kid[1], depth + 1)
      print(p.kid[0], depth + 1)
    
    
    Run

For `cligen/trie.nim` since I was already using a super wasteful uncompressed 
trie, I just punted on all that and spent memory proportional to the answer, 
i.e.: 
    
    
    proc collect*[T](n: Node[T], key: var string, d=0, pfx="", i=0): 
seq[tuple[k: string, n: Node[T]]] =
      if n == nil:
        return
      if n.term:
        result.add (k: (if i > 0: pfx[0..<i] & key else: key), n: n)
      for h, ch in n.kidc:
        key.setLen d + 1
        key[d] = ch
        result.add collect(n.kidp[h], key, d + 1, pfx, i)
    
    proc leaves[T](r: Node[T], depth=99, pfx="", i=0, d=0):
           seq[tuple[k: string, n: Node[T]]] =
      var key = ""                  #Would be nicer to do iterator w/state 
bounded
      collect(r, key, d, pfx, i)    #..by depth, but seemed tricky & 
unimportant.
    
    
    Run

It sounds like @JohnAD might find the trie variant of `leaves` an interesting 
challenge to do The Right Way. PRs welcome. ;-)

Reply via email to