Skip to content

Instantly share code, notes, and snippets.

@ratbeard
Created February 24, 2010 18:07
Show Gist options
  • Save ratbeard/313668 to your computer and use it in GitHub Desktop.
Save ratbeard/313668 to your computer and use it in GitHub Desktop.
// Function that builds both min and max functions since they use the same logic,
// the only thing difference being:
// - which Math function we use (min or max)
// - what the starting value is (Infinity or -Infinity)
// - what the comparison is (< or >) for the transform case.
// We can't curry this away without evaling or calling a function, so just pass in
// true if we want to use a greater than comparison, false to use less than.
//
// For the cases where no transform function is given we fast path out this trick:
// http://ejohn.org/blog/fast-javascript-maxmin/
// In this case with an object we just convert it to an array of its values,
// since this is probably a rare case and not worth the complexity to handle with a reduce.
//
// For the transform case, we need to remember both the min/max computed value, and the value
// that produced it. The value that produced it is what is returned.
var minMaxBuilder = function(mathFn, startValue, greater) {
return function(obj, transform, context) {
if (!transform) return mathFn.apply(Math, _.toArray(obj));
return reduce(obj, {computed: startValue}, function (memo, value, index, list) {
var computed = transform.call(context, value, index, list);
if (greater ? computed > memo.computed : computed < memo.computed)
memo.value = value, memo.computed = computed;
return memo;
}).value;
}
};
// Returns the maximum item, using Math.max by default.
// You can pass a transform function which is called for each value, in which case
// the first encountered value that produced the largest transformed value is returned
_.max = minMaxBuilder(Math.max, -Infinity, true);
// Returns the minimum item, using Math.min by default.
// You can pass a transform function which is called for each value, in which case
// the first encountered value that produced the smallest transformed value is returned
_.min = minMaxBuilder(Math.min, Infinity, false);
// Builds a wrapper over a function from some source.
// The wrapper function proceeds as follows:
// - adjusts arguments
// - applies the original method to an object with the adjusted arguments
// - builds a chain with around an object and returns it
//
//
// By default:
// - the arguments are not adjusted.
// overide by passing a function which is given the arguments and wrapper
// - the method is applied to _wrapped.
// override by passing an object to apply to (perhaps `_`)
// - the returned chain is built around the method result
// pass true to build around _wrapped instead
//
// The way options are passed is a little cryptic, but probably not worth passing
// them as an options hash unless this becomes public api, and if it needs to be
// more generic in which case the options should take in functions, like adjustArgs does,
// (in order to access things like this._wrapped).
var buildWrapperMethod = function (name, source, adjustArgs, applyTo, returnWrapped ) {
adjustArgs = adjustArgs || _.identity;
var args, result, toReturn, method = source[name];
return function () {
args = adjustArgs(arguments, this);
result = method.apply(applyTo || this._wrapped, args);
toReturn = returnWrapped ? this._wrapped : result;
return chained(toReturn, this._chain);
};
}
// build wrapper methods for each given function names, using the given builderOptions
// see usage below
var addToWrapperPrototype = function (names, options) {
each(names, function (name) {
wrapper.prototype[name] = buildWrapperMethod.apply(null, [name].concat(options))
});
}
var prependWrapped = function (args, wrapper) {
return [wrapper._wrapped].concat(_.toArray(args));
};
addToWrapperPrototype( // Underscore functions
_.functions(),
[_, prependWrapped]
);
addToWrapperPrototype( // Array mutator functions
'pop push reverse shift sort splice unshift'.split(' '),
[Array.prototype, false, false, true]
);
addToWrapperPrototype( // Array accessor functions
'concat join slice'.split(' '),
[Array.prototype]
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment