Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

min_by,max_by equivalent functions in javascript

Tags:

javascript

Is there any javascript library that contains functions like min_by,max_by in Ruby, which allow you pass a lambda as a criteria to compare. I found not such things in JQuery and underscore.js .

like image 295
user145055 Avatar asked Sep 17 '14 11:09

user145055


4 Answers

Chris's solution runs through the array three times; if the array is small, that's fine, but it's not ideal. You can use reduce to get that down to a single pass (with a sort of as-you-go version of a Schwartzian transform):

// the only difference between minBy and maxBy is the ordering
// function, so abstract that out
Array.prototype.minBy = function(fn) { 
  return this.extremumBy(fn, Math.min); 
};

Array.prototype.maxBy = function(fn) { 
  return this.extremumBy(fn, Math.max);
};

Array.prototype.extremumBy = function(pluck, extremum) {
  return this.reduce(function(best, next) {
    var pair = [ pluck(next), next ];
    if (!best) {
       return pair;
    } else if (extremum.apply(null, [ best[0], pair[0] ]) == best[0]) {
       return best;
    } else {
       return pair;
    }
  },null)[1];
}

Which works as advertised:

['albatross', 'dog', 'horse'].minBy( s => s.length )
// => "dog"

['albatross', 'dog', 'horse'].maxBy( s => s.length )
// => "albatross"

We could make it a little fancier by allowing the "plucker" to be an index or attribute name as well as a function:

const pluck = function(object, plucker) {
  const t = typeof plucker;
  switch (t) {
    case 'function':
      return plucker(object)
    case 'number':
    case 'string':
      return object[plucker]
    default:
      throw new TypeError(`Invalid type for pluck: ${t}`)
  }
}

Array.prototype.extremumBy = function(plucker, extremum) {
  return this.reduce(function(best, next) {
    var pair = [ pluck(next, plucker), next ];
...

And then this works, too:

['albatross', 'dog', 'horse'].minBy('length')
//=> 'dog'

['albatross', 'dog', 'horse'].maxBy('length')
//=> 'albatross'
like image 132
Mark Reed Avatar answered Nov 11 '22 09:11

Mark Reed


I use reduce

var items = [{value: 0}];
var min = items.reduce((seed,item) => { return (seed && seed.value < item.value) ? seed : item; },null);
var max = items.reduce((seed,item) => { return (seed && seed.value > item.value) ? seed : item; },null);

Edit: Can also be added to prototype

Array.prototype.minBy = function(pluck) {
    return this.reduce((min, x) => min && pluck(min) <= pluck(x) ? min : x, null)
}

Array.prototype.maxBy = function(pluck) {
    return this.reduce((max, x) => max && pluck(max) >= pluck(x) ? max : x, null)
}
like image 30
Thaina Yu Avatar answered Nov 11 '22 08:11

Thaina Yu


The example of min_by in the documentation has the following example:

a = %w(albatross dog horse)
a.min_by {|x| x.length }   #=> "dog"

So we can roughly translate that to JavaScript as:

var arr = ['albatross', 'dog', 'horse'];

function minBy(arr) {
  var result = arr.map(function (el) { return el.length; });
  var min = Math.min.apply(null, result);
  return arr[result.indexOf(min)];
}

minBy(arr); // dog

max_by would use Math.max.apply instead.

function maxBy(arr) {
  var result = arr.map(function (el) {
    return el.length;
  });
  var min = Math.max.apply(null, result);
  return arr[result.indexOf(min)];
}

maxBy(arr); // albatross

You could also amend the array prototype to get it more Rubyish.

if (!('minBy' in Array.prototype)) {
  Array.prototype.minBy = function (type) {
    var result = this.map(function (el) { return el[type]; });
    var min = Math.min.apply(null, result);
    return arr[result.indexOf(min)];    
  };
}

arr.minBy('length'); // dog

DEMO

like image 25
Andy Avatar answered Nov 11 '22 08:11

Andy


To use this in the same way as Ruby, ie call it on the array:

Array.prototype.minBy = function(lambda) {
    var lambdaFn;
    if (typeof(lambda) === "function") {
        lambdaFn = lambda;
    } else {
        lambdaFn = function(x){
            return x[lambda];
        }
    }
    var mapped = this.map(lambdaFn); 
    var minValue = Math.min.apply(Math, mapped); 
    return this[mapped.indexOf(minValue)];
}

So the ruby example becomes:

['albatross', 'dog', 'horse'].minBy(function(x){return x.length })  // = 'dog'

or:

['albatross', 'dog', 'horse'].minBy("length") // = 'dog'
like image 1
Chris Charles Avatar answered Nov 11 '22 10:11

Chris Charles