vim-easymotion icon indicating copy to clipboard operation
vim-easymotion copied to clipboard

Main grouping algorithm needs improvement

Open yangmillstheory opened this issue 8 years ago • 0 comments

For reference, I mean s:GroupingAlgorithmSCTree

https://github.com/easymotion/vim-easymotion/blob/342549e7a1e5b07a030803e0e4b6f0415aa51275/autoload/EasyMotion.vim#L805-L892

The algorithm is correct, but should be cleaned up and refactored, because:

  • there are unused variables, causing confusion
  • this call to reverse is unnecessary
  • building this index (which happens at every recursive call but is always the same) is unnecessary
  • it has too many responsibilities (you can tell by the existence of variables scoped to the entire function, but used only in one or two places):
    • creating a list of child node counts per key
    • creating an index of keys into that list
    • recursively building the tree

I would propose splitting s:GroupingAlgorithmSCTree out into two functions, the first is a helper function

function! s:get_children_counts(hits_rem)
  " returns a list corresponding to s:jump_tokens; each
  " count represents how many hits are in the subtree
  " rooted at the corresponding jump token
  let n_jump_tokens = len(s:jump_tokens)
  let n_hits = repeat([0], n_jump_tokens)
  let hits_rem = a:hits_rem

  let is_first_lvl = 1
  while hits_rem > 0
    " if we can't fit all the hits in the first lvl,
    " fit the remainder starting from the last jump token
    let n_children = is_first_lvl
          \ ? 1
          \ : n_jump_tokens - 1
    for j in range(n_jump_tokens)
      let n_hits[j] += n_children
      let hits_rem -= n_children
      if hits_rem <= 0
        let n_hits[j] += hits_rem
        break
      endif
    endfor
    let is_first_lvl = 0
  endwhile

  return reverse(n_hits)
endfunction

and the second below is the grouping algorithm.

function! s:GetJumpTree(hits)
  " returns a tree where non-leaf nodes are jump tokens and leaves are coordinates
  " (tuples) of hits.
  "
  " each level of the tree is filled such that the average path depth of the tree
  " is minimized and the closest hits come first first.
  let tree = {}

  " i: index into hits
  " j: index into jump tokens
  let i = 0
  let j = 0
  for n_children in s:get_children_counts(len(a:hits))
    let node = s:jump_tokens[j]
    if n_children == 1
      let tree[node] = a:hits[i]
    elseif n_children > 1
      let tree[node] = s:GetJumpTree(a:hits[i:i + n_children - 1])
    else
      continue
    endif
    let j += 1
    let i += n_children
  endfor
  return tree
endfunction

The algorithm and results are the same; I've tested it on various inputs and can include in the PR.

I'm open to preserving the existing naming (I use "hits" for "targets" and "jump tokens" for "keys") and but I think this implementation is cleaner and makes it easier for others to understand and extend, and cuts the amount of grouping algorithm code almost by half.

yangmillstheory avatar Nov 26 '17 19:11 yangmillstheory