DataViz
DataViz

Reputation: 31

Check if values in Array within a range

I need to check if an array I have is within certain ranges.

I have an array:

var times = [79, 118, 145, 245, 688, 833, 934, 956, 1019, -339, -324, -265, 65, 81, 83, 121, 151, 154, 359]

and want to get the count of values falling within these ranges:

var ranges = ['0-10', '11-20', '21-30', '31-40', '41-50', '51-60', '61 +'];

Here is the code I have so far..

$.each(times, function (i, datum) 
{
    if (datum <= 0) 
        sum1 += 1;
    else if (datum > 10 && datum <= 20) 
        sum2 += 1;
    else if (datum > 20 && datum <= 30)
        sum3 += 1;
    else if (datum > 30 && datum <= 40)
        sum4 += 1;
    else if (datum > 40 && datum <= 50)
        sum5 += 1;
    else if (datum > 50 && datum <= 60)
        sum6 += 1;
    else if (datum > 60)
        sum7 += 1;
}

How would I get an object of ranges and sums. e.g. {'0-10': 2, '11-20': 19, '21-30': 45}

Upvotes: 2

Views: 4016

Answers (6)

Manuel Duarte
Manuel Duarte

Reputation: 1040

Even if it's an old question I think this deserves an updated answer as javascript has evolved a lot. This is what I would do:

var times = [79, 118, 145, 245, 688, 833, 934, 956, 1019, -339, -324, -265, 65, 81, 83, 121, 151, 154, 359]
var ranges = ['0-10', '11-20', '21-30', '31-40', '41-50', '51-60', '61'];
const result = {}

ranges.forEach(range=>{
 const rangeNumbers = range.split("-")
let inRangeNumbers

if(rangeNumbers.length>1){
inRangeNumbers = times.filter(t=>(t>=parseInt(rangeNumbers[0]) && t<=parseInt(rangeNumbers[1])))
}else{
 inRangeNumbers = times.filter(t=>(t>=parseInt(rangeNumbers[0]))
}
 
result[range] = {range: range, amount: inRangeNumbers.length, values: inRangeNumbers}
})

}
return result

Upvotes: 0

King King
King King

Reputation: 63317

You can try this code:

var times = [79, 118, 145, 245, 688, 833, 15, 934, 956, 1019, -339, -324, -265, 65, 81, 83, 121, 151, 154, 359];
var ranges = ['0-10', '11-20', '21-30', '31-40', '41-50', '51-60', '61 +'];
var rangeStops = [0,11,21,31,41,51,61];
var rangeCounts = [0];
//sort the times first 
times.sort(function(a,b){return a - b});
var k = 0;
for(var i = 0; i < times.length; i++){    
  if(rangeStops[k] > times[i]) continue;
  else if(rangeStops[k+1] <= times[i]&&k != rangeStops.length - 1){
    rangeCounts[++k] = 0;          
    i--;
  } else rangeCounts[k]++;
}
//output
ranges = ranges.map(function(e,i){    
  var result = {};
  result[e] = rangeCounts[i];
  return result;
});
//log it and see the result in console window
console.log(ranges);

Demo.

Note that I added a 15 value to the array times, and the result should be:

'0-10': 0
'11-20': 1 //the 15 value
'21-30': 0
'31-40': 0
'41-50': 0
'51-60': 0
'61+' : 16 //all the other positive values

Upvotes: 0

Sukima
Sukima

Reputation: 10064

No SO answer would be complete without an Underscore answer:

var times = [79, 118, 145, 245, 688, 833, 934, 956, 1019, -339, -324, -265, 65, 81, 83, 121, 151, 154, 359]

var ranges = ['0-10', '11-20', '21-30', '31-40', '41-50', '51-60', '61 +'];

var counts = _(ranges).reduce(function(memo, range) {
  memo[range] = 0;
  return memo;
}, {});

counts.outOfBounds = 0;

var rangePattern    = /(\d+)\s*-\s*(\d+)/;
var moreThanPattern = /(\d+)\s*\+/;
var lessThanPattern = /-\s*(\d+)/;

_(times).forEach(function(value) {
  var range = _(ranges).find(function(testRange) {
    var match;
    switch (false) {
      case !(match = testRange.match(rangePattern)):
        return (value >= match[1] && value <= match[2]);
      case !(match = testRange.match(moreThanPattern)):
        return (value >= match[1]);
      case !(match = testRange.match(lessThanPattern)):
        return (value <= match[1]);
      default:
        return false;
    }
  });
  counts[range || 'outOfBounds']++;
});

console.log(counts);

See it in action at this JSBin

Upvotes: 0

elclanrs
elclanrs

Reputation: 94101

I would recommend outputting a collection, rather than an object because it will be easier to work with; since it's an array of objects you can filter, map, and reduce, which you can't do very comfortably on objects. For example:

function getStepRanges(ranges, times) {
  return ranges.map(function(range) {
    var ranges = range.split(/[-+]/)
    var min = +ranges[0]
    var max = +ranges[1] || Infinity
    return times.reduce(function(acc, x) {
      // Note that your range is not inclusive
      // you may want to do "x >= min" instead
      if (x > min && x <= max) {
        acc.range = range
        acc.values = (acc.values||[]).concat(x)
      }
      return acc
    },{})
  })
}

Using it on this dummy data:

var times = [1,2,3,4,5,10,11,12,13,14,21,23,24,25,26,31,32,33,34,35,41,42,43,44,45,51,52,53,54,55,61,62,63]
var ranges = ['0-10', '11-20', '21-30', '31-40', '41-50', '51-60', '61+']

It will return this collection:

[ { range: '0-10', values: [ 1, 2, 3, 4, 5, 10 ] },
  { range: '11-20', values: [ 12, 13, 14 ] },
  { range: '21-30', values: [ 23, 24, 25, 26 ] },
  { range: '31-40', values: [ 32, 33, 34, 35 ] },
  { range: '41-50', values: [ 42, 43, 44, 45 ] },
  { range: '51-60', values: [ 52, 53, 54, 55 ] },
  { range: '61+', values: [ 62, 63 ] } ]

http://jsbin.com/fowop/1/edit

With that data then you can do what you need, for example getting the maximum values:

var result = getStepRanges(ranges, times)

// Easy, we have a collection!
// but careful, it mutates the original object
// you may want to use an "extend" helper to clone it first
var maxRanges = result.map(function(x) {
  x.max = Math.max.apply(0, x.values)
  return x
})

console.log(maxRanges)
/*[ { range: '0-10', values: [ 1, 2, 3, 4, 5, 10 ], max: 10 },
  { range: '11-20', values: [ 12, 13, 14 ], max: 14 },
  { range: '21-30', values: [ 23, 24, 25, 26 ], max: 26 },
  { range: '31-40', values: [ 32, 33, 34, 35 ], max: 35 },
  { range: '41-50', values: [ 42, 43, 44, 45 ], max: 45 },
  { range: '51-60', values: [ 52, 53, 54, 55 ], max: 55 },
  { range: '61+', values: [ 62, 63 ], max: 63 } ]*/

Upvotes: 1

juvian
juvian

Reputation: 16068

var times = [79, 118, 145, 245, 688, 833, 934, 956, 1019, -339, -324, -265, 65, 81, 83, 121, 151, 154, 359];

var ranges = ['0-10', '11-20', '21-30', '31-40', '41-50', '51-60', '61 +'];

function getObj(range){
    var obj={};
    range=range.replace("+","-Infinity");
    var arr=range.split("-");
    return {sum:0,min:Number(arr[0]),max:Number(arr[1])};
}

var sums={};
for(var i=0;i<ranges.length;i++) sums[ranges[i]]=getObj(ranges[i]);
for(var i=0;i<times.length;i++){
    for(var j=0;j<ranges.length;j++){
        var sum=sums[ranges[j]];
        if(times[i]>=sum.min && times[i] <= sum.max){sum.sum+=1}
    }
}
console.log(sums)

Full fiddle: http://jsfiddle.net/6RRyL/1/

Upvotes: 1

njzk2
njzk2

Reputation: 39386

Assuming you rewrite your range as such:

rangeList = $.map(ranges, function(item) {
    values = item.split('-')
    return {min: parseInt(values[0]), max: parseInt(values[1]) || Number.POSITIVE_INFINITY, label: item}
})

You have a nice structure. Now you have to test each value against each range. (Technically you can stop at the first that match, so you probably can use any instead of each and return true if the range is met).

var sums = {}
$.each(times, function(i, datum) {
    $.each(rangeList, function(rangeIndex, range) {
        if (datum > range.min && datum <= range.max) {
            sums[range.label] = (sums[range.label] || 0) + 1
        }
    })
})

Of course in your case, that returns {61 +: 16}

Upvotes: 0

Related Questions