From 8ee06cb8f20edaa441b76c5658d5c8ce169106d6 Mon Sep 17 00:00:00 2001 From: Mike Clark Date: Thu, 7 Dec 2017 08:01:15 +0800 Subject: [PATCH 1/4] Create _.function.permutations.js Start of PR for https://github.com/node4good/lodash-contrib/issues/47 --- common-js/_.function.permutations.js | 141 +++++++++++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 common-js/_.function.permutations.js diff --git a/common-js/_.function.permutations.js b/common-js/_.function.permutations.js new file mode 100644 index 0000000..f73fdff --- /dev/null +++ b/common-js/_.function.permutations.js @@ -0,0 +1,141 @@ +module.exports = function (_) { + /** + * Lodash mixins for combinatorics + * Inspired by python itertools: https://docs.python.org/2.7/library/itertools.html + * + * Usage: + * permutations([0,1,2],2) // [[0,1],[0,2],[1,0],[1,2],[2,0],[2,1]] + * combinations([0,1,2],2) // [[0,1],[0,2],[1,2]] + * combinations_with_replacement([0,1,2],2)// [[0,0],[0,1],[0,2],[1,1],[1,2],[2,2]] + * product([0,1,2],[0,1,2]) // [[0,0],[0,1],[0,2],[1,0],[1,1],[1,2],[2,0],[2,1],[2,2]] + * + * Multiple input types: + * product('me','hi') + * product({who:['me','you'],say:['hi','by']}) + * product(['me','you'],['hi','by']) + * product(['me','hi']) + * combinations([0,1,2,3],2) + * permutations([1,2,3],2) + * permutations('cat',2) + */ + var _ = require('lodash') + + /** + * Generate all combination of arguments when given arrays or strings + * e.g. [['Ben','Jade','Darren'],['Smith','Miller']] to [['Ben','Smith'],[..]] + * e.g. 'the','cat' to [['t', 'c'],['t', 'a'], ...] + **/ + function _cartesianProductOf(args) { + if (arguments.length>1) args=_.toArray(arguments); + + // strings to arrays of letters + args=_.map(args, opt=>typeof opt==='string'?_.toArray(opt):opt) + + return _.reduce(args, function(a, b) { + return _.flatten(_.map(a, function(x) { + return _.map(b, function(y) { + return _.concat(x,[y]); + }); + }), true); + }, [ [] ]); + } + + /** Generate all combination of arguments from objects + * {Object} opts - An object or arrays with keys describing options {firstName:['Ben','Jade','Darren'],lastName:['Smith','Miller']} + * {Array} - An array of objects e.g. [{firstName:'Ben',LastName:'Smith'},{..] + **/ + function _cartesianProductObj(optObj){ + var keys = _.keys(optObj); + var opts = _.values(optObj); + var combs = _cartesianProductOf(opts); + return _.map(combs,function(comb){ + return _.zipObject(keys,comb); + }); + } + + /** + * Generate the cartesian product of input objects, arrays, or strings + * + * + * product('me','hi') + * // => [["m","h"],["m","i"],["e","h"],["e","i"]] + * + * product([1,2,3],['a','b','c'] + * // => [[1,"a"],[1,"b"],[1,"c"],[2,"a"],[2,"b"],[2,"c"],[3,"a"],[3,"b"],[3,"c"]] + * + * product({who:['me','you'],say:['hi','by']}) + * // => [{"who":"me","say":"hi"},{"who":"me","say":"by"},{"who":"you","say":"hi"},{"who":"you","say":"by"}] + * + * // It also takes in a single array of args + * product(['me','hi']) + * // => [["m","h"],["m","i"],["e","h"],["e","i"]] + */ + function product(opts){ + if (arguments.length===1 && !_.isArray(opts)) + return _cartesianProductObj(opts) + else if (arguments.length===1) + return _cartesianProductOf(opts) + else + return _cartesianProductOf(arguments) + } + + /** + * Generate permutations, in all possible orderings, with no repeat values + * + * + * permutations([1,2,3],2) + * // => [[1,2],[1,3],[2,1],[2,3],[3,1],[3,2] + * + * permutations('cat',2) + * // => [["c","a"],["c","t"],["a","c"],["a","t"],["t","c"],["t","a"]] + */ + function permutations(obj, n){ + if (typeof obj=='string') obj = _.toArray(obj) + n = n?n:obj.length + // make n copies of keys/indices + for (var j = 0, nInds=[]; j < n; j++) {nInds.push(_.keys(obj)) } + // get product of the indices, then filter to remove the same key twice + var arrangements = product(nInds).filter(pair=>pair[0]!==pair[1]) + return _.map(arrangements,indices=>_.map(indices,i=>obj[i])) + } + + + + /** + * Generate n combinations of an object with no repeat values in each combination. + * + * + * combinations([0,1,2,3],2) + * // => [[0,1],[0,2],[0,3],[1,2],[1,3],[2,3]] + */ + function combinations(obj,n){ + /* filter out keys out of order, e.g. [0,1] is ok but [1,0] isn't */ + function isSorted(arr) { + return _.every(arr, function (value, index, array) { + return index === 0 || String(array[index - 1]) <= String(value); + }); + } + // array with n copies of the keys of obj + return _(permutations(_.keys(obj),n)) + .filter(isSorted) + .map(indices=>_.map(indices,i=>obj[i])) + .value() + } + + /** + * Generate n combinations with repeat values. + * + * + * combinations_with_replacement([0,1,2,3],2) + * // => [[0,0],[0,1],[0,2],[0,3],[1,1],[1,2],[1,3],[2,2],[2,3],[3,3]] + */ + function combinations_with_replacement(obj,n){ + if (typeof obj=='string') obj = _.toArray(obj) + n = n?n:obj.length + // make n copies of keys/indices + for (var j = 0, nInds=[]; j < n; j++) {nInds.push(_.keys(obj)) } + // get product of the indices, then filter to keep elements in order + var arrangements = product(nInds).filter(pair=>pair[0]<=pair[1]) + return _.map(arrangements,indices=>_.map(indices,i=>obj[i])) + } +}; From 5aaba6d352b448fca8ba4bf79c65dcb6d5bb57b1 Mon Sep 17 00:00:00 2001 From: Mike Clark Date: Sun, 8 Dec 2019 22:57:09 +0000 Subject: [PATCH 2/4] fix permutations for n>2 https://gist.github.com/wassname/a882ac3981c8e18d2556, thanks to visangela on gist --- common-js/_.function.permutations.js | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/common-js/_.function.permutations.js b/common-js/_.function.permutations.js index f73fdff..f8d30f2 100644 --- a/common-js/_.function.permutations.js +++ b/common-js/_.function.permutations.js @@ -89,16 +89,22 @@ module.exports = function (_) { * permutations('cat',2) * // => [["c","a"],["c","t"],["a","c"],["a","t"],["t","c"],["t","a"]] */ - function permutations(obj, n){ - if (typeof obj=='string') obj = _.toArray(obj) - n = n?n:obj.length - // make n copies of keys/indices - for (var j = 0, nInds=[]; j < n; j++) {nInds.push(_.keys(obj)) } - // get product of the indices, then filter to remove the same key twice - var arrangements = product(nInds).filter(pair=>pair[0]!==pair[1]) - return _.map(arrangements,indices=>_.map(indices,i=>obj[i])) - } - + function permutations(obj, n){ + if (typeof obj=='string') obj = _.toArray(obj) + n = n?n:obj.length + // make n copies of keys/indices + let nInds=[]; + for (var j = 0; j < n; j++) {nInds.push(_.keys(obj)) } + // get product of the indices, then filter to remove the same key twice + // var arrangements = product(nInds).filter(pair=>pair[0]!==pair[1]) // this line only removes duplicates from the first two elements. + let arrangements = product(nInds); + let out=[] + for (let j=0; j< arrangements.length;j++ ) { + let outt = arrangements[j].filter((value, index, self)=> {return self.indexOf(value) === index}) + if (outt.length === arrangements[j].length) out.push(outt) + } + return _.map(out,indices=>_.map(indices,i=>obj[i])) + } /** From d0465d4be940cce495d38991c7a59449509300d4 Mon Sep 17 00:00:00 2001 From: wassname Date: Mon, 9 Dec 2019 08:11:53 +0800 Subject: [PATCH 3/4] add passing tests --- ...mutations.js => _.function.permutators.js} | 16 +++++++++--- test/browserified.html | 1 + test/dist-min.html | 1 + test/function.permutators.js | 25 +++++++++++++++++++ test/index.html | 1 + 5 files changed, 40 insertions(+), 4 deletions(-) rename common-js/{_.function.permutations.js => _.function.permutators.js} (96%) create mode 100644 test/function.permutators.js diff --git a/common-js/_.function.permutations.js b/common-js/_.function.permutators.js similarity index 96% rename from common-js/_.function.permutations.js rename to common-js/_.function.permutators.js index f8d30f2..822e452 100644 --- a/common-js/_.function.permutations.js +++ b/common-js/_.function.permutators.js @@ -18,8 +18,8 @@ module.exports = function (_) { * permutations([1,2,3],2) * permutations('cat',2) */ - var _ = require('lodash') + /** * Generate all combination of arguments when given arrays or strings * e.g. [['Ben','Jade','Darren'],['Smith','Miller']] to [['Ben','Smith'],[..]] @@ -33,10 +33,10 @@ module.exports = function (_) { return _.reduce(args, function(a, b) { return _.flatten(_.map(a, function(x) { - return _.map(b, function(y) { - return _.concat(x,[y]); + return _.map(b, function (y) { + return _.cat(x,[y]); }); - }), true); + }), false); }, [ [] ]); } @@ -144,4 +144,12 @@ module.exports = function (_) { var arrangements = product(nInds).filter(pair=>pair[0]<=pair[1]) return _.map(arrangements,indices=>_.map(indices,i=>obj[i])) } + + _.mixin({ + combinations, + combinations_with_replacement, + product, + permutations + }) + }; diff --git a/test/browserified.html b/test/browserified.html index ffde57c..9a9c700 100644 --- a/test/browserified.html +++ b/test/browserified.html @@ -18,6 +18,7 @@ + diff --git a/test/dist-min.html b/test/dist-min.html index 601870b..4bedace 100644 --- a/test/dist-min.html +++ b/test/dist-min.html @@ -18,6 +18,7 @@ + diff --git a/test/function.permutators.js b/test/function.permutators.js new file mode 100644 index 0000000..4ef0e58 --- /dev/null +++ b/test/function.permutators.js @@ -0,0 +1,25 @@ +$(document).ready(function () { + + module("lodash.function.permutators"); + + test("product", function () { + deepEqual(_.product('me', 'hi'), [["m", "h"], ["m", "i"], ["e", "h"], ["e", "i"]], 'should return product of strings'); + deepEqual(_.product({ who: ['me', 'you'], say: ['hi', 'by'] }), [{ "who": "me", "say": "hi" }, { "who": "me", "say": "by" }, { "who": "you", "say": "hi" }, { "who": "you", "say": "by" }], 'should return product of object keys and lists'); + deepEqual(_.product(['me', 'you'], ['hi', 'by']), [["me", "hi"], ["me", "by"], ["you", "hi"], ["you", "by"]], 'should return product of lists'); + }); + + test("combinations", function () { + deepEqual(_.combinations([1, 2, 3], 2), [[1, 2], [1, 3], [2, 3]], 'should return combinations of list'); + }); + + test("combinations_with_replacement", function () { + deepEqual(_.combinations_with_replacement([1, 2, 3], 2), [[1, 1], [1, 2], [1, 3], [2, 2], [2, 3], [3, 3]], 'should return combinations_with_replacement of list'); + }); + + test("permutations", function () { + deepEqual(_.permutations([1, 2, 3], 2), [[1, 2], [1, 3], [2, 1], [2, 3], [3, 1], [3, 2]], 'should return n=2 permutations of list'); + deepEqual(_.permutations([1, 2, 3], 3), [[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]], 'should return n=3 permutations of list'); + deepEqual(_.permutations('cat', 2), [["c", "a"], ["c", "t"], ["a", "c"], ["a", "t"], ["t", "c"], ["t", "a"]], 'should return n=2 permutations of string'); + }); + +}); diff --git a/test/index.html b/test/index.html index 2ea3e8b..a41f018 100644 --- a/test/index.html +++ b/test/index.html @@ -18,6 +18,7 @@ + From 3042a670dc629aa5b74ab459a6371452e6f685b9 Mon Sep 17 00:00:00 2001 From: wassname Date: Mon, 9 Dec 2019 08:34:17 +0800 Subject: [PATCH 4/4] fixed uglify warnings, es6->es5 --- common-js/_.function.permutators.js | 277 ++++++++++++++-------------- test/function.permutators.js | 4 +- 2 files changed, 142 insertions(+), 139 deletions(-) diff --git a/common-js/_.function.permutators.js b/common-js/_.function.permutators.js index 822e452..5899893 100644 --- a/common-js/_.function.permutators.js +++ b/common-js/_.function.permutators.js @@ -1,155 +1,158 @@ module.exports = function (_) { - /** - * Lodash mixins for combinatorics - * Inspired by python itertools: https://docs.python.org/2.7/library/itertools.html - * - * Usage: - * permutations([0,1,2],2) // [[0,1],[0,2],[1,0],[1,2],[2,0],[2,1]] - * combinations([0,1,2],2) // [[0,1],[0,2],[1,2]] - * combinations_with_replacement([0,1,2],2)// [[0,0],[0,1],[0,2],[1,1],[1,2],[2,2]] - * product([0,1,2],[0,1,2]) // [[0,0],[0,1],[0,2],[1,0],[1,1],[1,2],[2,0],[2,1],[2,2]] - * - * Multiple input types: - * product('me','hi') - * product({who:['me','you'],say:['hi','by']}) - * product(['me','you'],['hi','by']) - * product(['me','hi']) - * combinations([0,1,2,3],2) - * permutations([1,2,3],2) - * permutations('cat',2) - */ + /** + * Lodash mixins for combinatorics + * Inspired by python itertools: https://docs.python.org/2.7/library/itertools.html + * + * Usage: + * permutations([0,1,2],2) // [[0,1],[0,2],[1,0],[1,2],[2,0],[2,1]] + * combinations([0,1,2],2) // [[0,1],[0,2],[1,2]] + * combinationsWithReplacement([0,1,2],2)// [[0,0],[0,1],[0,2],[1,1],[1,2],[2,2]] + * product([0,1,2],[0,1,2]) // [[0,0],[0,1],[0,2],[1,0],[1,1],[1,2],[2,0],[2,1],[2,2]] + * + * Multiple input types: + * product('me','hi') + * product({who:['me','you'],say:['hi','by']}) + * product(['me','you'],['hi','by']) + * product(['me','hi']) + * combinations([0,1,2,3],2) + * permutations([1,2,3],2) + * permutations('cat',2) + */ - - /** - * Generate all combination of arguments when given arrays or strings - * e.g. [['Ben','Jade','Darren'],['Smith','Miller']] to [['Ben','Smith'],[..]] - * e.g. 'the','cat' to [['t', 'c'],['t', 'a'], ...] - **/ - function _cartesianProductOf(args) { - if (arguments.length>1) args=_.toArray(arguments); - // strings to arrays of letters - args=_.map(args, opt=>typeof opt==='string'?_.toArray(opt):opt) + /** + * Generate all combination of arguments when given arrays or strings + * e.g. [['Ben','Jade','Darren'],['Smith','Miller']] to [['Ben','Smith'],[..]] + * e.g. 'the','cat' to [['t', 'c'],['t', 'a'], ...] + **/ + function _cartesianProductOf(args) { + if (arguments.length > 1) { args = _.toArray(arguments); } - return _.reduce(args, function(a, b) { - return _.flatten(_.map(a, function(x) { - return _.map(b, function (y) { - return _.cat(x,[y]); - }); - }), false); - }, [ [] ]); - } + // strings to arrays of varters + args = _.map(args, function (opt) { return typeof opt === 'string' ? _.toArray(opt) : opt }) - /** Generate all combination of arguments from objects - * {Object} opts - An object or arrays with keys describing options {firstName:['Ben','Jade','Darren'],lastName:['Smith','Miller']} - * {Array} - An array of objects e.g. [{firstName:'Ben',LastName:'Smith'},{..] - **/ - function _cartesianProductObj(optObj){ - var keys = _.keys(optObj); - var opts = _.values(optObj); - var combs = _cartesianProductOf(opts); - return _.map(combs,function(comb){ - return _.zipObject(keys,comb); - }); - } + return _.reduce(args, function (a, b) { + return _.flatten(_.map(a, function (x) { + return _.map(b, function (y) { + return _.cat(x, [y]); + }); + }), false); + }, [[]]); + } - /** - * Generate the cartesian product of input objects, arrays, or strings - * - * - * product('me','hi') - * // => [["m","h"],["m","i"],["e","h"],["e","i"]] - * - * product([1,2,3],['a','b','c'] - * // => [[1,"a"],[1,"b"],[1,"c"],[2,"a"],[2,"b"],[2,"c"],[3,"a"],[3,"b"],[3,"c"]] - * - * product({who:['me','you'],say:['hi','by']}) - * // => [{"who":"me","say":"hi"},{"who":"me","say":"by"},{"who":"you","say":"hi"},{"who":"you","say":"by"}] - * - * // It also takes in a single array of args - * product(['me','hi']) - * // => [["m","h"],["m","i"],["e","h"],["e","i"]] - */ - function product(opts){ - if (arguments.length===1 && !_.isArray(opts)) - return _cartesianProductObj(opts) - else if (arguments.length===1) - return _cartesianProductOf(opts) - else - return _cartesianProductOf(arguments) - } + /** Generate all combination of arguments from objects + * {Object} opts - An object or arrays with keys describing options {firstName:['Ben','Jade','Darren'],lastName:['Smith','Miller']} + * {Array} - An array of objects e.g. [{firstName:'Ben',LastName:'Smith'},{..] + **/ + function _cartesianProductObj(optObj) { + var keys = _.keys(optObj); + var opts = _.values(optObj); + var combs = _cartesianProductOf(opts); + return _.map(combs, function (comb) { + return _.zipObject(keys, comb); + }); + } + + /** + * Generate the cartesian product of input objects, arrays, or strings + * + * + * product('me','hi') + * // => [["m","h"],["m","i"],["e","h"],["e","i"]] + * + * product([1,2,3],['a','b','c'] + * // => [[1,"a"],[1,"b"],[1,"c"],[2,"a"],[2,"b"],[2,"c"],[3,"a"],[3,"b"],[3,"c"]] + * + * product({who:['me','you'],say:['hi','by']}) + * // => [{"who":"me","say":"hi"},{"who":"me","say":"by"},{"who":"you","say":"hi"},{"who":"you","say":"by"}] + * + * // It also takes in a single array of args + * product(['me','hi']) + * // => [["m","h"],["m","i"],["e","h"],["e","i"]] + */ + function product(opts) { + if (arguments.length === 1 && !_.isArray(opts)) { + return _cartesianProductObj(opts) + } + else if (arguments.length === 1) { + return _cartesianProductOf(opts) + } + else { + return _cartesianProductOf(arguments) + } + } - /** - * Generate permutations, in all possible orderings, with no repeat values - * - * - * permutations([1,2,3],2) - * // => [[1,2],[1,3],[2,1],[2,3],[3,1],[3,2] - * - * permutations('cat',2) - * // => [["c","a"],["c","t"],["a","c"],["a","t"],["t","c"],["t","a"]] - */ - function permutations(obj, n){ - if (typeof obj=='string') obj = _.toArray(obj) - n = n?n:obj.length + /** + * Generate permutations, in all possible orderings, with no repeat values + * + * + * permutations([1,2,3],2) + * // => [[1,2],[1,3],[2,1],[2,3],[3,1],[3,2] + * + * permutations('cat',2) + * // => [["c","a"],["c","t"],["a","c"],["a","t"],["t","c"],["t","a"]] + */ + function permutations(obj, n) { + if (typeof obj == 'string') { obj = _.toArray(obj) } + n = n ? n : obj.length; // make n copies of keys/indices - let nInds=[]; - for (var j = 0; j < n; j++) {nInds.push(_.keys(obj)) } + var nInds = []; + for (var j = 0; j < n; j++) { nInds.push(_.keys(obj)) } // get product of the indices, then filter to remove the same key twice // var arrangements = product(nInds).filter(pair=>pair[0]!==pair[1]) // this line only removes duplicates from the first two elements. - let arrangements = product(nInds); - let out=[] - for (let j=0; j< arrangements.length;j++ ) { - let outt = arrangements[j].filter((value, index, self)=> {return self.indexOf(value) === index}) - if (outt.length === arrangements[j].length) out.push(outt) + var arrangements = product(nInds); + var out = [] + for (var j = 0; j < arrangements.length; j++) { + var outt = arrangements[j].filter(function (value, index, self) { return self.indexOf(value) === index }) + if (outt.length === arrangements[j].length) { out.push(outt) } } - return _.map(out,indices=>_.map(indices,i=>obj[i])) + return _.map(out, function (indices) { return _.map(indices, function (i) { return obj[i] }) }) } - /** - * Generate n combinations of an object with no repeat values in each combination. - * - * - * combinations([0,1,2,3],2) - * // => [[0,1],[0,2],[0,3],[1,2],[1,3],[2,3]] - */ - function combinations(obj,n){ - /* filter out keys out of order, e.g. [0,1] is ok but [1,0] isn't */ - function isSorted(arr) { - return _.every(arr, function (value, index, array) { - return index === 0 || String(array[index - 1]) <= String(value); - }); - } - // array with n copies of the keys of obj - return _(permutations(_.keys(obj),n)) - .filter(isSorted) - .map(indices=>_.map(indices,i=>obj[i])) - .value() - } + /** + * Generate n combinations of an object with no repeat values in each combination. + * + * + * combinations([0,1,2,3],2) + * // => [[0,1],[0,2],[0,3],[1,2],[1,3],[2,3]] + */ + function combinations(obj, n) { + /* filter out keys out of order, e.g. [0,1] is ok but [1,0] isn't */ + function isSorted(arr) { + return _.every(arr, function (value, index, array) { + return index === 0 || String(array[index - 1]) <= String(value); + }); + } + // array with n copies of the keys of obj + return _(permutations(_.keys(obj), n)) + .filter(isSorted) + .map(function (indices) { return _.map(indices, function (i) { return obj[i] }) }) + .value() + } - /** - * Generate n combinations with repeat values. - * - * - * combinations_with_replacement([0,1,2,3],2) - * // => [[0,0],[0,1],[0,2],[0,3],[1,1],[1,2],[1,3],[2,2],[2,3],[3,3]] - */ - function combinations_with_replacement(obj,n){ - if (typeof obj=='string') obj = _.toArray(obj) - n = n?n:obj.length - // make n copies of keys/indices - for (var j = 0, nInds=[]; j < n; j++) {nInds.push(_.keys(obj)) } - // get product of the indices, then filter to keep elements in order - var arrangements = product(nInds).filter(pair=>pair[0]<=pair[1]) - return _.map(arrangements,indices=>_.map(indices,i=>obj[i])) - } + /** + * Generate n combinations with repeat values. + * + * + * combinationsWithReplacement([0,1,2,3],2) + * // => [[0,0],[0,1],[0,2],[0,3],[1,1],[1,2],[1,3],[2,2],[2,3],[3,3]] + */ + function combinationsWithReplacement(obj, n) { + if (typeof obj == 'string') { obj = _.toArray(obj) } + n = n ? n : obj.length + // make n copies of keys/indices + for (var j = 0, nInds = []; j < n; j++) { nInds.push(_.keys(obj)) } + // get product of the indices, then filter to keep elements in order + var arrangements = product(nInds).filter(function (pair) { return pair[0] <= pair[1] }) + return _.map(arrangements, function (indices) { return _.map(indices, function (i) { return obj[i] }) }) + } _.mixin({ - combinations, - combinations_with_replacement, - product, - permutations - }) - + combinations: combinations, + combinationsWithReplacement: combinationsWithReplacement, + product: product, + permutations: permutations + }) + }; diff --git a/test/function.permutators.js b/test/function.permutators.js index 4ef0e58..38aeb9a 100644 --- a/test/function.permutators.js +++ b/test/function.permutators.js @@ -12,8 +12,8 @@ $(document).ready(function () { deepEqual(_.combinations([1, 2, 3], 2), [[1, 2], [1, 3], [2, 3]], 'should return combinations of list'); }); - test("combinations_with_replacement", function () { - deepEqual(_.combinations_with_replacement([1, 2, 3], 2), [[1, 1], [1, 2], [1, 3], [2, 2], [2, 3], [3, 3]], 'should return combinations_with_replacement of list'); + test("combinationsWithReplacement", function () { + deepEqual(_.combinationsWithReplacement([1, 2, 3], 2), [[1, 1], [1, 2], [1, 3], [2, 2], [2, 3], [3, 3]], 'should return combinationsWithReplacement of list'); }); test("permutations", function () {