Created
February 24, 2010 18:07
-
-
Save ratbeard/313668 to your computer and use it in GitHub Desktop.
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
// 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); |
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
// 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