PDA

View Full Version : Simple Number Ranges


rnd me
10-28-2009, 04:10 AM
One thing that i've always missed in javascript was some type of range feature.
firefox has array comprehensions and generators, but most browsers don't support that, and it can't really be re-created using javascript 1.5.

the prototype library offers a Range utility, but i did not like how it requires an extra object and method call to use.

Needing something simple, fast, and flexible, i came up with the following function. It's served me well, so i thought i'd share.

Given two numbers as arguments, it returns a new range function that expects just one argument: a number.

Calling the range with a number as arguments[0] will return a Boolean indicating whether or not the number you passed is within the range's range.




function Range(n1, n2){
if(!n2){n2=0;}
if(n1>n2){n2=[n1,n1=n2][0];}
function range(n){return n>=range.min && n<=range.max;}
range.toString=function _ts(){return "( Range("+(range.min)+","+(range.max)+") )";}
range.min=n1;
range.max=n2;
return range;
}//end Range()



usage examples

test a couple numbers with a new range:

var rng=Range(10,20);
alert(rng(5))//==false
alert(rng(15))//==true



the order of the numbers given to Range does not matter:
var rng=Range(20,10);
alert(rng(5))//==false
alert(rng(15))//==true


you can change the range's min/max after creation:
var rng=Range(10,20);
alert(rng(5))//==false
rng.min=3;
alert(rng(5))//==true


range has it's own eval/parse-friendly String flavor:
var rng=Range(0, 500);
alert( String(rng) )//==="( Range(0,500) )"


you can use a new range 'on the spot' for a quick compare:
var x=15;
alert( Range(10,20)(x) )//===true


Range also works great for filtering arrays of numbers ([].filter required):
[5,10,15,99,20,25].filter( Range(10, 20) ); //===[10,15,20]


If you can think of any more uses, please don't be shy, post them here!

Trinithis
10-28-2009, 07:15 AM
firefox has array comprehensions [...], but most browsers don't support that, and it can't really be re-created using javascript 1.5.

You should have asked! Here's some brand new code for you:


var comprehension = (function () {

var map = function (f, xs) {
var ys = [];
for (var i = 0; i < xs.length; ++i) {
ys [i] = f (xs [i]);
}
return ys;
};

var concat = function (xss) {
var xs = [];
for (var i = 0; i < xss.length; ++i) {
Array.prototype.push.apply (xs, xss [i]);
}
return xs;
};

var bind = function (xs, f) {
return concat (map (f, xs));
};

return function (out, lists, preds) {
if (lists.length == 0) {
return [];
}
var vals = [];
var n = lists.length;
var rbind = function (i) {
if (i < n) {
var list = lists [i];
if (typeof list == "function") {
list = list.apply (this, vals);
}
return bind (list, function (x) {
vals [i] = x;
return rbind (i + 1);
});
}
else {
for (var j = 0; j < preds.length; ++j) {
if ( ! preds [j].apply (this, vals)) {
return [];
}
}
return [out.apply (this, vals)];
}
}
return rbind (0);
};

}) ();


Example:

function gcd (a, b) {
if (a < 0) {
a = -a;
}
if (b < 0) {
b = -b;
}
if (b > a) {
var temp = a;
a = b;
b = temp;
}
while (true) {
a %= b;
if (a == 0) {
return b;
}
b %= a;
if (b == 0) {
return a;
}
}
return b;
}

function coprime (x, y) {
return gcd (x, y) == 1;
}

function enumFrom1ToNSub1 (n) {
var xs = [];
for (var i = 1; i < n; ++i) {
xs.push (i);
}
return xs;
}

function take (n, xs) {
var ys = [];
for (var i = 0; i < n; ++i) {
ys.push (xs [i]);
}
return ys;
}

var result = comprehension (
function (i, j) { return ['(', i, ',', j, ')'].join (""); }
, [enumFrom1ToNSub1 (9), enumFrom1ToNSub1]
, [coprime]
);

document.writeln (take (10, result));

rnd me
10-30-2009, 02:18 AM
I realized that it would be nice to use many ranges in once.

You can use Ranges for traditional range-finding, but you can also use it for decision logic, replacing switch and if.

given a number it can determine:
1. if the number is in any part of the defined range
2. what section of the range it falls within (by index number or name)

By default, it gives 0 for misses, or the (slot#-1) of the argument that begins the matching sub-range.

That sounds complicated, but it's really simple:


basic usage:
var r=Ranges(10,20,30);
r(5)//===0
r(15)//===1
r(25)//===2
r(35)//===0




the Ranges function:

function Ranges(r){
var LUT={}, hasLUT=0;

if(typeof arguments[arguments.length-1]==="object"){
hasLUT=1;
LUT=arguments[arguments.length-1];
}
function range(n){
var r=range.ranges, mx=r.length;
if( n<r[0] || n>r[mx-1] ){ return LUT[0] !==undefined? LUT[0]:0; }
for(var i=mx; i; i--){
if( n > r[i-1] ){return LUT[i]||i;}
};
return LUT[0]||0;
};

range.ranges=((r && r.length && r.sort) ? r : [
].slice.call(arguments).slice(0, hasLUT?-1:9999 ) ).sort(function(a,b){
return a-b;}).concat();
range.toString=function _ts(){
return "( Range("+(range.ranges)+") )";}
return range;
}//end Ranges()


These examples explain it better than i can:


works without an extra var:
Ranges(10,20,30) (15); //===1

get a boolean if a number is in any range:
var num=15;
!! Ranges(10,20)( num ); //===true


with conversion table (last argument as array):
var r=Ranges(10,20,30, [false,"low","high"]);
[r(2), r(12), r(22), r(32)];//==[false, "low", "high", false];

the conversion table is an array where the elements reflect the "status" of a corresponding match in the arguments defining the ranges.
you can even create "holes" to eliminate nested if statements.



// rules: <10 is void, 10-20 is low, 20-25 is void, 25-30 is high, >30 is void:

var r=Ranges(10,20,25,30, [false,"low",false, "high"]);
[r(2), r(12), r(22), r(32)];//==[false, "low", false, false]


disclaimer: new code, but tested in FF3, IE8