diff --git a/build.js b/build.js index e0ce84ab6..09688ab0f 100755 --- a/build.js +++ b/build.js @@ -1060,6 +1060,16 @@ ].join('\n')); } + // replace `_.contains` + source = source.replace(/^( +)function contains[\s\S]+?\n\1}/m, [ + ' function contains(collection, target) {', + ' var length = collection ? collection.length : 0;', + " return typeof length == 'number'", + ' ? indexOf(collection, target) > -1', + ' : some(collection, function(value) { return value === target; });', + ' }' + ].join('\n')); + // replace `_.difference` source = source.replace(/^( +)function difference[\s\S]+?\n\1}/m, [ ' function difference(array) {', @@ -1198,9 +1208,6 @@ ' }' ].join('\n')); - // remove string support from `_.contains` - source = source.replace(/return *\(toString\.call.+?stringClass[\s\S]+?;/, 'return indexOf(collection, target) > -1;'); - // remove `arguments` object check from `_.isEqual` source = source.replace(/ *\|\| *className *== *argsClass/, ''); @@ -1382,7 +1389,7 @@ '', ' var index = 0,', ' source = "__p += \'",', - " variable = options.variable;", + ' variable = options.variable;', '', ' var reDelimiters = RegExp(', " (options.escape || reNoMatch).source + '|' +", diff --git a/lodash.js b/lodash.js index 17a8dbfe1..32cfbd49d 100644 --- a/lodash.js +++ b/lodash.js @@ -442,7 +442,7 @@ * @private * @param {Array} array The array to search. * @param {Mixed} value The value to search for. - * @param {Number} [fromIndex=0] The index to start searching from. + * @param {Number} [fromIndex=0] The index to search from. * @param {Number} [largeSize=30] The length at which an array is considered large. * @returns {Boolean} Returns `true` if `value` is found, else `false`. */ @@ -1792,7 +1792,8 @@ /** * Checks if a given `target` element is present in a `collection` using strict - * equality for comparisons, i.e. `===`. + * equality for comparisons, i.e. `===`. If `fromIndex` is negative, it is used + * as the offset from the end of the collection. * * @static * @memberOf _ @@ -1800,28 +1801,35 @@ * @category Collections * @param {Array|Object|String} collection The collection to iterate over. * @param {Mixed} target The value to check for. + * @param {Number} [fromIndex=0] The index to search from. * @returns {Boolean} Returns `true` if the `target` element is found, else `false`. * @example * - * _.contains([1, 2, 3], 3); + * _.contains([1, 2, 3], 1); * // => true * + * _.contains([1, 2, 3], 1, 2); + * // => false + * * _.contains({ 'name': 'moe', 'age': 40 }, 'moe'); * // => true * * _.contains('curly', 'ur'); * // => true */ - function contains(collection, target) { - var length = collection ? collection.length : 0; + function contains(collection, target, fromIndex) { + var index = -1, + length = collection ? collection.length : 0; + + fromIndex = (fromIndex < 0 ? nativeMax(0, length + fromIndex) : fromIndex) || 0; if (typeof length == 'number') { return (toString.call(collection) == stringClass - ? collection.indexOf(target) - : indexOf(collection, target) + ? collection.indexOf(target, fromIndex) + : indexOf(collection, target, fromIndex) ) > -1; } return some(collection, function(value) { - return value === target; + return ++index >= fromIndex && value === target; }); } @@ -2610,8 +2618,8 @@ * @category Arrays * @param {Array} array The array to search. * @param {Mixed} value The value to search for. - * @param {Boolean|Number} [fromIndex=0] The index to start searching from or - * `true` to perform a binary search on a sorted `array`. + * @param {Boolean|Number} [fromIndex=0] The index to search from or `true` to + * perform a binary search on a sorted `array`. * @returns {Number} Returns the index of the matched value or `-1`. * @example * @@ -2726,15 +2734,16 @@ } /** - * Gets the index at which the last occurrence of `value` is found using - * strict equality for comparisons, i.e. `===`. + * Gets the index at which the last occurrence of `value` is found using strict + * equality for comparisons, i.e. `===`. If `fromIndex` is negative, it is used + * as the offset from the end of the collection. * * @static * @memberOf _ * @category Arrays * @param {Array} array The array to search. * @param {Mixed} value The value to search for. - * @param {Number} [fromIndex=array.length-1] The index to start searching from. + * @param {Number} [fromIndex=array.length-1] The index to search from. * @returns {Number} Returns the index of the matched value or `-1`. * @example * diff --git a/lodash.min.js b/lodash.min.js index c30a9335c..f904d16d3 100644 --- a/lodash.min.js +++ b/lodash.min.js @@ -10,12 +10,12 @@ e.d+",r="+e.d+";if(!"+e.d+")return r;"+e.k+";",e.b?(r+="var k=j.length;h=-1;if(t yt.call(e);if(!Vt[u]||It&&v(e))return e;var a=u==Lt,n=a||(u==_t?on(e):n)}if(!n||!t)return n?a?gt.call(e):rn({},e):e;n=e.constructor;switch(u){case At:case Ot:return new n(+e);case Mt:case Pt:return new n(e);case Dt:return n(e.source,rt.exec(e))}s||(s=[]),o||(o=[]);for(u=s.length;u--;)if(s[u]==e)return o[u];var f=a?n(e.length):{};return s.push(e),o.push(f),(a?an:Zt)(e,function(e,n){f[n]=y(e,t,r,s,o)}),f}function b(e){var t=[];return Yt(e,function(e,n){S(e)&&t.push(n)}),t.sort()}function w(e){var t= {};return Zt(e,function(e,n){t[e]=n}),t}function E(e,t,s,o){if(e===t)return 0!==e||1/e==1/t;if(e==r||t==r)return e===t;var u=yt.call(e);if(u!=yt.call(t))return i;switch(u){case At:case Ot:return+e==+t;case Mt:return e!=+e?t!=+t:0==e?1/e==1/t:e==+t;case Dt:case Pt:return e==t+""}var a=u==Lt||u==kt;if(It&&!a&&(a=v(e))&&!v(t))return i;if(!a){if(e.__wrapped__||t.__wrapped__)return E(e.__wrapped__||e,t.__wrapped__||t);if(u!=_t||Ut&&("function"!=typeof e.toString&&"string"==typeof (e+"")||"function"!=typeof t.toString&&"string"==typeof (t+"")))return i;var u=e.constructor,f=t.constructor;if(u!=f&&(!S(u)||!(u instanceof u&&S(f)&&f instanceof f)))return i}s||(s=[]),o||(o=[]);for(u=s.length;u--;)if(s[u]==e)return o[u]==t;var u=-1,f=n,l=0;s.push(e),o.push(t);if(a){l=e.length;if(f=l==t.length)for(;l--&&(f=E(e[l],t[l],s,o)););return f}for(var c in e)if(dt.call(e,c)&&(l++,!dt.call(t,c)||!E(e[c],t[c],s,o)))return i;for(c in t)if(dt.call(t,c)&&!(l--))return i;if(Ht)for(;7>++u;)if(c=ft[u],dt.call(e,c)&&(!dt.call -(t,c)||!E(e[c],t[c],s,o)))return i;return n}function S(e){return"function"==typeof e}function x(e,t,n){var i=arguments,s=0,o=2,u=i[3],a=i[4];n!==K&&(u=[],a=[],o=i.length);for(;++sr&&(r=n,o=e)});else for(;++io&&(o=e[i]);return o}function M(e,t){var n=[];return an(e,function(e){n.push(e[t])}),n}function _(e,t,n,r){var s=3>arguments.length,t=f(t,r);return an(e,function(e,r,o){n=s?(s=i,e):t(n,e,r,o)}),n}function D(e,t,n,r){var s=e,o=e?e.length:0,u=3>arguments.length;if("number"!=typeof o)var a=un(e),o=a.length;else Rt&&yt -.call(e)==Pt&&(s=e.split(""));return an(e,function(e,f,l){f=a?a[--o]:--o,n=u?(u=i,s[f]):t.call(r,n,s[f],f,l)}),n}function P(e,t,n){var r,t=f(t,n);return an(e,function(e,n,i){return!(r=t(e,n,i))}),!!r}function H(e,t,n){if(e)return t==r||n?e[0]:gt.call(e,0,t)}function B(e,t){for(var n=-1,r=e?e.length:0,i=[];++nn?Tt(0,i+n):n||0)-1;else if(n)return r=I(e,t),e[r]===t?r:-1; -for(;++r>>1,n(e[r])j(a,h))(n||l)&&a.push(h),u.push(r)}return u}function R -(e,t){return Wt||bt&&2|{(\/]|\[\D|\b(?:delete|in|instanceof|new|typeof|void)\b/ +(t,c)||!E(e[c],t[c],s,o)))return i;return n}function S(e){return"function"==typeof e}function x(e,t,n){var i=arguments,s=0,o=2,u=i[3],a=i[4];n!==K&&(u=[],a=[],o=i.length);for(;++sn?Tt(0,i+n):n)||0;return"number"==typeof i?-1<(yt.call(e)==Pt?e.indexOf(t,n):j(e,t,n)):P(e,function(e){return++r>=n&&e===t})}function C(e,t,r){var i=n,t=f(t,r);return an(e,function(e,n,r){return i=!!t(e,n,r)}),i}function k(e,t,n){var r=[],t=f(t,n);return an(e,function(e,n,i){t(e,n,i)&&r.push(e)}),r}function L(e,t,r){var i,t=f(t,r);return P(e,function(e,r,s){return t(e,r,s)&&(i=e,n)}),i}function A(e,t,n){var r=-1,i=e?e.length:0,s=Array("number"==typeof i?i:0),t=f(t,n);if(sn(e))for(;++rr&&(r=n,o=e)});else for(;++io&&(o=e[i]);return o}function M(e,t){var n=[];return an(e,function(e){n.push(e[t])}),n}function _(e,t,n,r){var s=3>arguments.length,t=f(t,r);return an(e,function(e,r,o){n=s?(s=i,e):t(n,e,r,o)}),n}function D(e,t,n,r){var s=e,o=e?e.length:0,u=3>arguments.length;if("number"!=typeof +o)var a=un(e),o=a.length;else Rt&&yt.call(e)==Pt&&(s=e.split(""));return an(e,function(e,f,l){f=a?a[--o]:--o,n=u?(u=i,s[f]):t.call(r,n,s[f],f,l)}),n}function P(e,t,n){var r,t=f(t,n);return an(e,function(e,n,i){return!(r=t(e,n,i))}),!!r}function H(e,t,n){if(e)return t==r||n?e[0]:gt.call(e,0,t)}function B(e,t){for(var n=-1,r=e?e.length:0,i=[];++nn?Tt(0,i+n):n||0)-1;else if( +n)return r=I(e,t),e[r]===t?r:-1;for(;++r>>1,n(e[r])j(a,h))(n||l)&&a.push +(h),u.push(r)}return u}function R(e,t){return Wt||bt&&2|{(\/]|\[\D|\b(?:delete|in|instanceof|new|typeof|void)\b/ ,Z=/&(?:amp|lt|gt|quot|#x27);/g,et=/\b__p\+='';/g,tt=/\b(__p\+=)''\+/g,nt=/(__e\(.*?\)|\b__t\))\+'';/g,rt=/\w*$/,it=/(?:__e|__t=)\(\s*(?![\d\s"']|this\.)/g,st=RegExp("^"+($.valueOf+"").replace(/[.*+?^=!:${}()|[\]\/\\]/g,"\\$&").replace(/valueOf|for [^\]]+/g,".+?")+"$"),ot=/($^)/,ut=/[&<>"']/g,at=/['\n\r\t\u2028\u2029\\]/g,ft="constructor hasOwnProperty isPrototypeOf propertyIsEnumerable toLocaleString toString valueOf".split(" "),lt=Math.ceil,ct=V.concat,ht=Math.floor,pt=st.test(pt=Object.getPrototypeOf )&&pt,dt=$.hasOwnProperty,vt=V.push,mt=$.propertyIsEnumerable,gt=V.slice,yt=$.toString,bt=st.test(bt=gt.bind)&&bt,wt=st.test(wt=Array.isArray)&&wt,Et=e.isFinite,St=e.isNaN,xt=st.test(xt=Object.keys)&&xt,Tt=Math.max,Nt=Math.min,Ct=Math.random,kt="[object Arguments]",Lt="[object Array]",At="[object Boolean]",Ot="[object Date]",Mt="[object Number]",_t="[object Object]",Dt="[object RegExp]",Pt="[object String]",Ht,Bt,jt=(jt={0:1,length:1},V.splice.call(jt,0,1),jt[0]),Ft=n;(function(){function e(){this .x=1}var t=[];e.prototype={valueOf:1,y:1};for(var n in new e)t.push(n);for(n in arguments)Ft=!n;Ht=!/valueOf/.test(t),Bt="x"!=t[0]})(1);var It=!v(arguments),qt="x"!=gt.call("x")[0],Rt="xx"!="x"[0]+Object("x")[0];try{var Ut=("[object Object]",yt.call(e.document||0)==_t)}catch(zt){}var Wt=bt&&/\n|Opera/.test(bt+yt.call(e.opera)),Xt=xt&&/^.+$|true/.test(xt+!!e.attachEvent),Vt={};Vt[kt]=Vt["[object Function]"]=i,Vt[Lt]=Vt[At]=Vt[Ot]=Vt[Mt]=Vt[_t]=Vt[Dt]=Vt[Pt]=n;var $t={"boolean":i,"function":n,object diff --git a/test/test-build.js b/test/test-build.js index 836774f3a..11ce7c46c 100644 --- a/test/test-build.js +++ b/test/test-build.js @@ -636,6 +636,7 @@ equal(object.fn(), 2, '_.bind: ' + basename); ok(lodash.clone(array, true)[0] === array[0], '_.clone should be shallow: ' + basename); + equal(lodash.contains([1, 2, 3], 1, 2), true, '_.contains should ignore `fromIndex`: ' + basename); equal(lodash.every([true, false, true]), false, '_.every: ' + basename); object = { 'length': 0, 'splice': Array.prototype.splice }; diff --git a/test/test.js b/test/test.js index 184e81cde..aed2df804 100644 --- a/test/test.js +++ b/test/test.js @@ -262,6 +262,33 @@ QUnit.module('lodash.contains'); (function() { + _.each({ + 'an array': [1, 2, 3, 1, 2, 3], + 'an object': { 'a': 1, 'b': 2, 'c': 3, 'd': 1, 'e': 2, 'f': 3 }, + 'a string': '123123' + }, + function(collection, key) { + test('should work with ' + key + ' and a positive `fromIndex`', function() { + equal(_.contains(collection, 1, 2), true); + }); + + test('should work with ' + key + ' and a `fromIndex` >= collection\'s length', function() { + equal(_.contains(collection, 1, 6), false); + equal(_.contains(collection, undefined, 6), false); + equal(_.contains(collection, 1, 8), false); + equal(_.contains(collection, undefined, 8), false); + }); + + test('should work with ' + key + ' and a negative `fromIndex`', function() { + equal(_.contains(collection, 2, -3), true); + }); + + test('should work with ' + key + ' and a negative `fromIndex` <= negative collection\'s length', function() { + equal(_.contains(collection, 1, -6), true); + equal(_.contains(collection, 2, -8), true); + }); + }); + _.each({ 'literal': 'abc', 'object': Object('abc')