Tuesday, March 11, 2014

Zethos - $3.5 million in 1kb


Zethos is my $3.5 million dollar entry to js1k 2014. It's a speed reading tool, allowing almost anyone to read at 500 words per minute (the average person reading 250wpm [1]). By keeping the text centered within the readers view, the reader is able to reduce costly eye movements and increase their reading speed.

Zethos was inspired by Spritz, who recently raised a $3.5 million seed round for their speed-reading technology. However their software is closed and their system is not developer friendly so I decided to make my own. Without further ado, here is what $3.5 million code looks like:

parse = function(str) {
  // Logic
  // strings will be broken out into words
  // find the focus point of the word
  // if, when the word is shifted to its focus point
  //   one end prodtrudes from either end more than 7 chars
  //   re-run parser after hyphenating the words
  
  // focus point
  // start in middle of word (default focus point)
  // move left until you hit a vowel, then stop
  
  // hyphenating
  //    if <= 7 chars
  //      return self
  //    if <= 10
  //    return x, {3}
  //    if <= 14 chars
  //    return {7},{7}
  //    else
  //    return {7},hyphenated{x}
  
  hyphenate = function(str) {
    with(str)
    return length <= 7 ? str : length <= 10 ? slice(0,length - 3)+'- '+slice(length-3) : slice(0,7)+'- '+hyphenate(slice(7))
  }
  
  // return 2d array with word and focus point
  return str.trim().split(/[\s\n]+/).reduce(function(words, str) {
    with(str) {
      // focus point
      focus = (length-1)/2|0
      
      for(j=focus;j>=0;j--)
        if (/[aeiou]/.test(str[j])) {
          focus = j
          break
        }
      
      t = 60000/500 // 500 wpm
        
      if (length > 6)
        t+=t/4
        
      if (~indexOf(','))
        t+=t/2
        
      if(/[\.!\?;]$/.test(str))
        t+= t*1.5
      
      return length >= 15 || length - focus > 7 ? words.concat(parse(hyphenate(str))) : words.concat([[str, focus, t]])
    }
  }, [])
}

p = function() {
  index = 0
  input = parse(i.textContent);
  playing = !playing
  playing && loop()
}

loop = function() {
  w = input[index++] || p()
  o.innerHTML = Array(8 - w[1]).join('&nbsp;')+w[0].slice(0,w[1])+'<v>'+w[0][w[1]]+'</v>'+w[0].slice(w[1]+1)
  playing && setTimeout(loop, w[2])
}

p()

I've done Javascript Golfing before, so getting this code to fit in at less than 1kb was trivial. With the help of Grunt, I was easily able to compile my code by running it through the Google Closure Compiler and then JSCrush. They key to the code however is it's recursive nature. The self-calling methods allow the code to be extremely concise and not reliant on a buffer or global variables. The Hyphenate method for example will recursively hyphenate the word it's given based on length for arbitrarily sized words using recursion.

As far as implementation goes, the part that could be most improved for production applications would be to add proper hyphenation. There's a paper written about algorithmic hyphenation, and even a npm library to do this, however fitting it in 1kb was not feasible.

Lastly, my first js1k attempt this year did not work out. It was intended to be a neural network handwriting recognition engine (TinyNet), but unfortunately the weights of the network proved to be highly non-compressible.

7 comments:

  1. Hey Zoli! Nice work getting it under 1k :) I put together a bookmarklet that grabs the text of the page you're reading and presents it in the same fashion. www.squirt.io. It's open source, embeddable on your blog, has some nice features. LMK what you think!

    ReplyDelete
    Replies
    1. Cool app! The optimal character (red) I think could be chosen a bit better though.

      Delete
  2. Nice concept,
    It would be wicked if this could work with something http://store.neurosky.com/products/mindwave-1 (an attention detector basically)
    and speed up when your attention raise!
    Also the thing can detect your eye blink, and you basically want to stop a bit the reader when this occurs ;-)
    I assume google glass already have thought about that.

    Future here we go.

    ReplyDelete
  3. Hy, this is great,
    I'd like to ask some improvement if you can
    add a bar to increase or decrease speed
    add a bar to increase decrease the lenght of substring
    and at least (maybe too complicate) add the opportunity set the number of token
    now you have only one word at a time. it would be great to try 2 word or more.
    My Idea would be to read 20-25 char at a time
    PS: .,!: should crlf the sentence

    example:

    Welcome to Zethos!
    This is a speed
    reading tool
    inspired by Spritz
    ($3.5mil).
    It's free and open
    source on GitHub.
    -Zolmeister

    ReplyDelete
    Replies
    1. Spritz's entry, which this code attempts (fairly successfully might I add) to replicate is based on a great deal of research. This research indicates that the visual cortex of the brain recognizes words spontaneously once the eye has moved to a certain point in the word. The idea is to position words such that your eye naturally falls at that point and never has to move. Introducing a 2nd word, or having words longer than 12 characters would force the eye to move again, which defeats the purpose of this technique.

      About 80% of your reading time is spent in saccades, fast eye movements. By eliminating those eye movements we should, in theory, be able to read up to 5 times faster, minus some fudge factor to represent the learning curve, long words and the few saccades we can't get rid of. Experimental results in the 900 to 1,000 wpm range seem to support that theory.

      Delete