Created
September 21, 2012 09:28
-
-
Save FGRibreau/3760576 to your computer and use it in GitHub Desktop.
My yesterday night 1 hour code rush: JavaScript Hash with Range-based keys
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* September 20 night: 1 hour code rush | |
* @FGRibreau | |
* | |
* Requirements | |
* ------------ | |
* The aim of this code rush is to enable developers to define a "range hash map" | |
* in a very expressive way (without if/then/else). | |
* | |
* Data-set | |
* -------- | |
* | |
* rules = | |
* Nb of days -> Precision levels set | |
* --------------------------------------- | |
* ] -∞ ; 1 ] -> {min, hour, day, mon} | |
* ] 1 ; 8 ] -> {hour, day, mon} | |
* ] 8 ; 62 ] -> {day, mon} | |
* ] 62 ; +∞ [ -> {mon} | |
* | |
* | |
* High-level API | |
* -------------- | |
* | |
* rules.getFor([days.number]) -> {} | |
* | |
* Note | |
* ---- | |
* | |
* - Only _.toArray() underscore method was used and it could easily be replaced by a Array::slice.call(). | |
* - The unit-tests use Qunit. | |
*/ | |
var rules = new Rules() | |
.add(range('] -∞ ; 1 ]'), precisions('min', 'hour', 'day', 'mon')) | |
.add(range('] 1 ; 8 ]'), precisions('hour', 'day', 'mon')) | |
.add(range('] 8 ; 62 ]'), precisions('day', 'mon')) | |
.add(range('] 62 ; +∞ ['), precisions('mon')); | |
/** | |
* Implementation | |
*/ | |
function Rules(){ | |
var rules = []; | |
this.add = function(range, value){ | |
if(!range.in){throw new Error("add(range, value), range must implement .in(Number) -> Boolean");} | |
rules.push(_.toArray(arguments)); | |
return this; | |
}; | |
this.getFor = function(n){ | |
var i = -1, iM = rules.length; | |
while(i++ < iM){ | |
if(rules[i][0].in(n)){return rules[i][1];} | |
} | |
return undefined; | |
}; | |
}; | |
function range(str){ | |
function car(s){return (s || '')[0];} | |
function cdr(s){return (s || '').substring(1);} | |
function cdl(s){return (s || '').substring(0, s.length-1);} | |
function last(s){return (s || '')[s.length-1];} | |
function toNumber(s){return s.indexOf('∞')>0 ? (s[0]==='-'?-1:1)/0 : parseInt(s, 10);} | |
function _range(){ | |
var L, R, Lincluded, Rincluded; | |
str = str.replace(/\s+/gi, '').split(';'); | |
this.Lincluded = Lincluded = car(str[0]) === '['; | |
this.L = L = toNumber(cdr(str[0])); | |
this.R = R = toNumber(cdl(str[1])); | |
this.Rincluded = Rincluded = last(str[1]) === ']'; | |
this.in = function(n){ | |
return ( | |
(!Lincluded && n > L) || (Lincluded && n >= L)) && ((!Rincluded && n < R) || (Rincluded && n <= R) | |
); | |
}; | |
} | |
return new _range(); | |
}; | |
function precisions(){return _.toArray(arguments);}; | |
/** | |
* Unit-tests | |
*/ | |
// Test | |
function checkRangeParse(str, Lincluded, L, R, Rincluded){ | |
var _range = range(str); | |
equal(_range.Lincluded, Lincluded, str + ' Lincluded =' + Lincluded); | |
equal(_range.L, L, str + ' L =' + L); | |
equal(_range.R, R, str + ' R =' + R); | |
equal(_range.Rincluded, Rincluded, str + ' Rincluded =' + Rincluded); | |
} | |
checkRangeParse('] -∞ ; 1 ]', false, -Infinity, 1, true); | |
checkRangeParse('] 1 ; 8 ]', false, 1 , 8 , true); | |
checkRangeParse('] 8 ; 62 ]', false, 8 , 62, true); | |
checkRangeParse('] 62 ; +∞ [', false, 62, +Infinity, false); | |
// Test | |
function inRange(_range, _in, expected){ | |
equal(range(_range).in(_in), expected, _range + '.in('+_in+') = ' + expected); | |
} | |
inRange('] -∞ ; 1 ]', -10, true); | |
inRange('] -∞ ; 1 ]', 0, true); | |
inRange('] -∞ ; 1 ]', 1, true); | |
inRange('] -∞ ; 1 ]', 2, false); | |
inRange('] 1 ; 8 ]', 1, false); | |
inRange('] 1 ; 8 ]', 2, true); | |
inRange('] 1 ; 8 ]', 8, true); | |
inRange('] 1 ; 8 ]', 9, false); | |
inRange('] 8 ; 62 ]', 8, false); | |
inRange('] 8 ; 62 ]', 9, true); | |
inRange('] 8 ; 62 ]', 62, true); | |
inRange('] 8 ; 62 ]', 63, false); | |
inRange('] 62 ; +∞ [', 61, false); | |
inRange('] 62 ; +∞ [', 62, false); | |
inRange('] 62 ; +∞ [', 63, true); | |
inRange('] 62 ; +∞ [', 890, true); | |
// Test | |
function valueFor(_range, expected){ | |
deepEqual(rules.getFor(_range), expected, "rules.getFor("+_range+") " + JSON.stringify(expected)); | |
} | |
valueFor(-1, ['min', 'hour', 'day', 'mon']); | |
valueFor(1, ['min', 'hour', 'day', 'mon']); | |
valueFor(2, ['hour', 'day', 'mon']); | |
valueFor(8, ['hour', 'day', 'mon']); | |
valueFor(9, ['day', 'mon']); | |
valueFor(62, ['day', 'mon']); | |
valueFor(63, ['mon']); | |
valueFor(6390, ['mon']); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Feel free to comment & improve !
#L54 .getFor() currently just perform a linear search and should be improved by sorting the ranges - as well as looking for overlapping range - at insert time.