xen-dev-utils
Version:
Utility functions used by the Scale Workshop ecosystem
192 lines • 7.43 kB
JavaScript
;
/**
* https://gist.github.com/axelpale/3118596
*
* Copyright 2012 Akseli Palén.
* Created 2012-07-15.
* Types added by Lumi Pakkanen on 2022-05-04
* Licensed under the MIT license.
*
* <license>
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
* </license>
*
* Implements functions to calculate combinations of elements in JS Arrays.
*
* Functions:
* kCombinations(set, k) -- Return all non-empty k-sized combinations in a set
* combinations(set) -- Return all non-empty combinations of the set
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.iterCombinations = exports.iterKCombinations = exports.combinations = exports.kCombinations = void 0;
/**
* K-combinations
*
* Examples:
* ```ts
* kCombinations([1, 2, 3], 1) // [[1], [2], [3]]
* kCombinations([1, 2, 3], 2) // [[1, 2], [1, 3], [2, 3]]
* kCombinations([1, 2, 3], 3) // [[1, 2, 3]]
* kCombinations([1, 2, 3], 4) // []
* kCombinations([1, 2, 3], 0) // []
* kCombinations([1, 2, 3], -1) // []
* kCombinations([], 0) // []
* ```
* @param set Array of objects of any type. They are treated as unique.
* @param k Size of combinations to search for.
* @returns Array of found non-empty combinations, size of a combination is k.
*/
function kCombinations(set, k) {
// There is no way to take e.g. sets of 5 elements from
// a set of 4.
// All sets with 0 elements are empty.
// A set with negative amount of elements makes no sense.
if (k > set.length || k <= 0) {
return [];
}
// K-sized set has only one K-sized subset.
if (k === set.length) {
return [set];
}
// There is N 1-sized subsets in a N-sized set.
if (k === 1) {
const combs = [];
for (let i = 0; i < set.length; i++) {
combs.push([set[i]]);
}
return combs;
}
// Assert {1 < k < set.length}
// Algorithm description:
// To get k-combinations of a set, we want to join each element
// with all (k-1)-combinations of the other elements. The set of
// these k-sized sets would be the desired result. However, as we
// represent sets with lists, we need to take duplicates into
// account. To avoid producing duplicates and also unnecessary
// computing, we use the following approach: each element i
// divides the list into three: the preceding elements, the
// current element i, and the subsequent elements. For the first
// element, the list of preceding elements is empty. For element i,
// we compute the (k-1)-computations of the subsequent elements,
// join each with the element i, and store the joined to the set of
// computed k-combinations. We do not need to take the preceding
// elements into account, because they have already been the i:th
// element so they are already computed and stored. When the length
// of the subsequent list drops below (k-1), we cannot find any
// (k-1)-combs, hence the upper limit for the iteration:
const combs = [];
for (let i = 0; i < set.length - k + 1; i++) {
// head is a list that includes only our current element.
const head = set.slice(i, i + 1);
// We take smaller combinations from the subsequent elements
const tailCombs = kCombinations(set.slice(i + 1), k - 1);
// For each (k-1)-combination we join it with the current
// and store it to the set of k-combinations.
for (let j = 0; j < tailCombs.length; j++) {
combs.push(head.concat(tailCombs[j]));
}
}
return combs;
}
exports.kCombinations = kCombinations;
/**
* Get all possible combinations of elements in a set.
*
* Examples:
* ```ts
* combinations([1, 2, 3]) // [[1], [2], [3], [1, 2], [1, 3], [2, 3], [1, 2, 3]]
* combinations([1]) // [[1]]
* ```
* @param set Array of objects of any type. They are treated as unique.
* @returns Array of arrays representing all possible non-empty combinations of elements in a set.
*/
function combinations(set) {
const combs = [];
// Calculate all non-empty k-combinations
for (let k = 1; k <= set.length; k++) {
const kCombs = kCombinations(set, k);
for (let i = 0; i < kCombs.length; i++) {
combs.push(kCombs[i]);
}
}
return combs;
}
exports.combinations = combinations;
/**
* K-combinations
* @param set Array of objects of any type. They are treated as unique.
* @param k Size of combinations to search for.
* @returns Generator of found combinations, size of a combination is k.
*/
function* iterKCombinations(set, k) {
// There is no way to take e.g. sets of 5 elements from
// a set of 4.
// All sets with 0 elements are empty.
// A set with negative amount of elements makes no sense.
if (k > set.length || k <= 0) {
return 0;
}
// K-sized set has only one K-sized subset.
if (k === set.length) {
yield set;
return 1;
}
// There is N 1-sized subsets in a N-sized set.
if (k === 1) {
for (let i = 0; i < set.length; i++) {
yield [set[i]];
}
return set.length;
}
// Generic algorithm with recursion.
let total = 0;
for (let i = 0; i < set.length - k + 1; i++) {
// head is a list that includes only our current element.
const head = set.slice(i, i + 1);
// We take smaller combinations from the subsequent elements
// For each (k-1)-combination we join it with the current
// and store it to the set of k-combinations.
for (const tailComb of iterKCombinations(set.slice(i + 1), k - 1)) {
yield head.concat(tailComb);
total++;
}
}
return total;
}
exports.iterKCombinations = iterKCombinations;
/**
* Get all possible combinations of elements in a set.
* @param set Array of objects of any type. They are treated as unique.
* @returns Generator of arrays representing all possible non-empty combinations of elements in a set.
*/
function* iterCombinations(set) {
// Calculate all non-empty k-combinations
let total = 0;
for (let k = 1; k <= set.length; k++) {
for (const kComb of iterKCombinations(set, k)) {
yield kComb;
total++;
}
}
return total;
}
exports.iterCombinations = iterCombinations;
//# sourceMappingURL=combinations.js.map