Sure, here it is.

I couldn't resist doing some simple benchmarks, just to verify my intuition. Very glad I did.

OK - here's the theory :

we're using variations of binary search to find the lowest numbered line that is visible, and then again to find the highest numbered line.

So at each stage we have an interval within which the right answer must lie.

1. Simple binary search : the next guess is the middle of the current interval.

This is simple, and very consistent for the number of guesses needed: the worst case is almost identical to the average/typical.

For my test case (25000 lines of heavily styled text), we need approx 14 guesses for the first calculaiton, and 8 for the second.

2. Simple Newton-Raphson (linear interpolation).

This is usually better than simple binary, *if* there is a reasonable correlation between the measurable values and the guessable values. In this case there - the height of any chunk of lines is reasonably correlated to the number of lines, though not exactly in all cases.

Usually this will take significantly fewer guesses (and in this case it takes around 8 + 4 compared to the 14+8 above).

BUT - the worst case can be much, much worse :-( Imagine a field with 1000 lines - the first 500 are in 5-point text, and the other 500 are in 1000 point text, scrolled forward by 400 lines; this makes our guessing VERY poor, and will take up to 200 or more guesses.

3. Use a mix of linear interpolation and binary search. I tried simply alternating them - one calculated guess, then one 'average' guess, then another calculated one, ....

This should significantly limit the worst case - but makes the average somewhat worse.

4. Use linear interpolation - but disallow very small (or very large) estimates.

This again limits the worst case (not quite so well), and makes the average only slightly worse.


I tried each of these strategies on a range of scroll positions in my test case field. Results were

(1) Binary   6283

(2) N-R      3757

(3) N-R/binary 4200

(4) N-R limited 3782


So - the script below does (4) above - linear interpolation, with the very small or very large percentage guesses disallowed. But, I've left in the code (commented out) to also do alternating binary guesses, so it can be easily used if your use case is very sensitive to extreme examples.

Note - this only uses the accurate height of a chunk function once it has almost determined its answer - up until then, it only needs approximate results to make the next guess, so no need for the extra work of doing the pixel-accurate calculation.

function visibleTextLines pT
   local vs, sw, m, t, b, L1, L2
   local t1, t2, t3  -- timing
   lock screen; lock messages
   put the vscroll of fld pT into vs
   put the scrollbarWidth of fld pT into sw
   put the margins of fld pT into m
   put (m,m,m,m) into m -- now we have always at least 4 items
   -- value of pixel within the field where top line will be
   put   (item 2 of m) + vs into t
   -- value of pixel within the field where bottom line will be
   put  - (item 4 of m)  + vs + the effective height of fld pT into b
   if the hscrollbar of fld pT then subtract sw from b

   put the millisecs into t1   -- timing
   put findTopLine(pT, t-5) into L1
   put the millisecs into t2   -- timing
   put findBottomLine(pT, b+5, L1) into L2
   put the millisecs into t3   -- timing
   --    put "times" && t2-t1 && t3-t2 &CR after fld "Flog" -- timing
   return (L1,L2)
end visibleTextLines


function findTopLine pFName, pPixel
-- for fld 'pFName', find the lowest numbered line which will be at or beyond pPixel

   local tBelow, tAbove, tGuess, tTotal
   local tTarget

   put 1 into tBelow
   put 0 into tHBelow

   put the number of lines in fld pFName into tAbove
   put the formattedheight of fld pFName into tHAbove

   put pPixel into tTarget

   -- we need to find the first line whose formattedheight is >= tTarget
   local tD, tIteration
   repeat with tIteration = 1 to 1000
      if tAbove = tBelow+1 then exit repeat
-- if tIteration mod 2 = 0 then -- alternate between lienar interpolation and simple binary halving
      --          put 0.5 into tD
      --       else
      put (tTarget-tHBelow) / (tHAbove-tHBelow) into tD
      --       end if

      -- limit very small or large [percentages
      if td < 0.05 then put 0.05 into tD
      if td > 0.95 then put 0.95 into tD

      put tBelow + trunc( (tAbove-tBelow) * tD) into tGuess
      if tGuess = tBelow then add 1 to tGuess
      --       put tGuess && tBelow && tAbove &CR after fld "Flog"

put the formattedheight of line 1 to tGuess of fld pFName into tt -- NB may be inaccurate !!
      if tt > tTarget then
         put tGuess into tAbove
         put tt into tHAbove
      else
         put tGuess into tBelow
         put tt into tHBelow
      end if
   end repeat
   -- now deal with the possible inaccuracy
   repeat 2 times -- should be no more than 1 !?
      if tBelow = 1 then exit repeat
      if getaccurateheight(1, tBelow-1, pFName) >= tTarget then
-- put "NEEDED TO SUBTRACT" && tBelow & CR after fld "Flog"
         subtract 1 from tBelow
      else
         exit repeat
      end if
   end repeat
   return tBelow
end findTopLine

function findBottomLine pFName, pPixel, pBelow
   -- for fld pFName, find highest numbered line before pPixel
   -- pBelow is the known lower bound
   local tBelow, tAbove, tGuess, tTotal
   local tTarget

   put pBelow-1 into tBelow
   put the formattedheight of line 1 to tBelow of fld pFName into tHBelow

-- limit to what could possibly fit into the actual field (min text size is 5) put min(the number of lines in fld pFName, pBelow + the effective height of fld pFName div 5) into tAbove
   put the formattedheight of line 1 to tAbove of fld pFName into tHAbove

   put pPixel into tTarget
   -- we need to find the last line whose formattedheight is < tTarget

   local tD, tIteration
   repeat with tIteration = 1 to 1000
      if tAbove = tBelow+1 then exit repeat

-- if tIteration mod 2 = 0 then -- alternate between lienar interpolation and simple binary halving
      --          put 0.5 into tD
      --       else
      put (tTarget-tHBelow) / (tHAbove-tHBelow) into tD
      --       end if

      if td < 0.1 then put 0.1 into tD
      if td > 0.9 then put 0.9 into tD
      put tBelow + trunc( (tAbove-tBelow) * tD) into tGuess
      if tGuess = tBelow then add 1 to tGuess

      put the formattedHeight of line 1 to tGuess of fld pFName into tt

      if tt > tTarget then
         put tGuess into tAbove
         put tt into tHAbove
      else
         put tGuess into tBelow
         put tt into tHBelow
      end if

   end repeat
   return tBelow
end findBottomLine

function getAccurateHeight N, M, pFld
   -- return the accurate height of the chunk from N .. M  of field pFld
   local t1, t2
   put the millisecs into t1
   put the formattedHeight of line N to M of fld pFld into h
   --    put "acc" && N && M && the millisecs-t1 &CR after fld "Flog"
   if line M of fld pFld is empty then
      --       put "getaccurate" && the millisecs &CR after fld "Flog"
      add the formattedheight of line M of fld pFLD to h
      --       put the styledText of fld pFld into tA
      --       put the vscroll of fld pFld into tempScroll
-- put tA[N+1] into tempA -- save a copy of the following line, complete with styling
      --       put tA[N] into tA[N+1]
      --       set the styledText of fld pFld to tA
      --       put the formattedHeight of line 1 to N of fld pFld into h
      --       put tempA into tA[N+1]
      --       set the styledText of fld pFld to tA
      --       set the vscroll of fld pFld to tempScroll
   end if
   add the spacebelow of line M of fld pFld to h
   put the millisecs into t2
   --    put "accurate" && N && M && t2-t1 &CR after fld "Flog"
   return h
end getAccurateHeight

-- Alex.







On 01/04/2017 17:42, hh via use-livecode wrote:
Alex T. wrote:
I set out to optimise the 'visibleLineNumber' function, and succeeded
in getting it down to approx 20% of the original version, by:
- reduce from 2 to 1 height calculations per iteration
- convert from recursive to iterative
- use Newton-Raphson style linear interpolation, with tweaks to avoid
the pathological end cases
- optimise the start values for finding the last visible line based on
the effective field height and min allowed text size
Alex,
please think about posting this. It is certainly valuable for other use
cases.

Hermann
(This post is NOT an April 1 - joke)

_______________________________________________
use-livecode mailing list
use-livecode@lists.runrev.com
Please visit this url to subscribe, unsubscribe and manage your subscription 
preferences:
http://lists.runrev.com/mailman/listinfo/use-livecode


_______________________________________________
use-livecode mailing list
use-livecode@lists.runrev.com
Please visit this url to subscribe, unsubscribe and manage your subscription 
preferences:
http://lists.runrev.com/mailman/listinfo/use-livecode

Reply via email to