Recently I built a little utility project for the fine people at UserTesting.com to help them get some insights into project lifecycle and “cycle time,” the time it takes any given project to move through pre-determined phases of the lifecycle. Hence, “Trello Cycles."
Seeing as how Trello‘s API is damn robust and they have a sweet piece of JS to help with authentication and some basic API requests I decided to do the whole thing client side and give Yeoman a try at the same time.
The webapp generator for Yeoman is pretty sweet: it scaffolds out some basic JS, CSS, and HTML; sets up Grunt tasks for local testing with live reload and building to a destination folder; and has support for CoffeeScript, HTML5 Boilerplate, Twitter Bootstrap, and RequireJS.
Grunt the Fuck?
Maybe I’m just not hip to the new JS jazz, but Gruntfiles make no fucking sense to me. Check out the auto-generated Gruntfile.js for Trello Cycles and the Rakefile for my Sinatra Boilerplate project. Both will build assets and run a development server (I realize there isn’t perfect feature parity so an apples-to-apples comparison is impossible, what I’m digging at here isn’t a functionality comparison) but I find the former damn near inscrutable and the latter generally more legible. It’s a minor thing. Sort of.
Turns out doing basic statistical work in
sum and mean (average) of an array of numbers isn’t as easy as just calling
[1, 2, 3].sum() or
[1, 2, 3].mean(). Instead, you’d have to do something
absolutely goofy looking like pass the array into a dedicated function like
arraySum([1, 2, 3]) and just looking at that makes my blood boil. Because I’ve
been using Ruby so much I’m used to calling methods directly on objects and
monkey patching core classes to do what I want so I decided I’d better get to
it. (Sidenote: I think there’s probably a discussion of encapsulation and API
consistency in here along the lines of Underscore
vs. core extension, but I’m not ready for that one yet).
This script adds some basic mathematical methods to the Array prototype chain including:
sumwhich depends on
meanwhich depends on
variancewhich gives you the variance of the set
stddevwhich gives you the standard deviation, or more simply the square root of the variance
With all of that, you can call
[1, 2, 3].sum() and
[1, 2, 3].mean() to your
1# max 2if "function" isnt typeof Array::max Array::max = -> "use strict" 3 4 # throw on null 5 throw new TypeError("Array.prototype.max called on null or undefined") if null is this or "undefined" is typeof this 6 7 Math.max.apply null, this 8 9# min 10if "function" isnt typeof Array::min 11 Array::min = -> 12 "use strict" 13 14 # throw on null 15 throw new TypeError("Array.prototype.min called on null or undefined") if null is this or "undefined" is typeof this 16 17 Math.min.apply null, this 18 19# median 20if "function" isnt typeof Array::median 21 Array::median = -> 22 "use strict" 23 24 # throw on null 25 throw new TypeError("Array.prototype.median called on null or undefined") if null is this or "undefined" is typeof this 26 27 # locals 28 sorted = @sort (a,b) => return a - b 29 length = @length >>> 0 30 half = Math.floor length/2 31 32 if length % 2 33 return this[half] 34 else 35 return [this[half - 1], this[half]].mean() 36 37# sum 38# depends on Array.prototype.reduce to work properly. 39# shim for that available here https://gist.github.com/evanleck/6418510 40if "function" isnt typeof Array::sum 41 Array::sum = -> 42 "use strict" 43 44 # throw on null 45 throw new TypeError("Array.prototype.sum called on null or undefined") if null is this or "undefined" is typeof this 46 47 @reduce (a, b) -> 48 a + b 49 50# mean 51if "function" isnt typeof Array::mean 52 Array::mean = -> 53 "use strict" 54 55 # throw on null 56 throw new TypeError("Array.prototype.mean called on null or undefined") if null is this or "undefined" is typeof this 57 58 length = @length >>> 0 59 60 @sum()/length 61 62# variance 63if "function" isnt typeof Array::variance 64 Array::variance = -> 65 "use strict" 66 67 # throw on null 68 throw new TypeError("Array.prototype.variance called on null or undefined") if null is this or "undefined" is typeof this 69 70 index = 0 71 length = @length >>> 0 72 mean = @mean() 73 powed =  74 75 # loop to generate powed 76 while length > index 77 continue unless @hasOwnProperty(index) 78 powed.push Math.pow(this[index] - mean, 2) 79 ++index 80 81 # return mean of powed 82 powed.mean() 83 84# standard deviation 85if "function" isnt typeof Array::stddev 86 Array::stddev = -> 87 "use strict" 88 89 # throw on null 90 throw new TypeError("Array.prototype.stddev called on null or undefined") if null is this or "undefined" is typeof this 91 92 # square root of variance 93 Math.sqrt @variance()