Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

_.permutation and _.combination support for arrays #56

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
158 changes: 158 additions & 0 deletions common-js/_.function.permutators.js
Original file line number Diff line number Diff line change
@@ -0,0 +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]]
* 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 varters
args = _.map(args, function (opt) { return typeof opt === 'string' ? _.toArray(opt) : opt })

return _.reduce(args, function (a, b) {
return _.flatten(_.map(a, function (x) {
return _.map(b, function (y) {
return _.cat(x, [y]);
});
}), false);
}, [[]]);
}

/** 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
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.
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, 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(function (indices) { return _.map(indices, function (i) { return obj[i] }) })
.value()
}

/**
* 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,
combinationsWithReplacement: combinationsWithReplacement,
product: product,
permutations: permutations
})

};
1 change: 1 addition & 0 deletions test/browserified.html
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
<script src="collections.walk.js"></script>
<script src="function.arity.js"></script>
<script src="function.combinators.js"></script>
<script src="function.permutators.js"></script>
<script src="function.dispatch.js"></script>
<script src="function.predicates.js"></script>
<script src="function.iterators.js"></script>
Expand Down
1 change: 1 addition & 0 deletions test/dist-min.html
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
<script src="collections.walk.js"></script>
<script src="function.arity.js"></script>
<script src="function.combinators.js"></script>
<script src="function.permutators.js"></script>
<script src="function.dispatch.js"></script>
<script src="function.predicates.js"></script>
<script src="function.iterators.js"></script>
Expand Down
25 changes: 25 additions & 0 deletions test/function.permutators.js
Original file line number Diff line number Diff line change
@@ -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("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 () {
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');
});

});
1 change: 1 addition & 0 deletions test/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
<script src="collections.walk.js"></script>
<script src="function.arity.js"></script>
<script src="function.combinators.js"></script>
<script src="function.permutators.js"></script>
<script src="function.dispatch.js"></script>
<script src="function.predicates.js"></script>
<script src="function.iterators.js"></script>
Expand Down