js-utl
Version:
A collection of JS utility functions to be used across several applications or libraries.
245 lines (226 loc) • 7.51 kB
JavaScript
/*
* Copyright (c) 2022 Anton Bagdatyev (Tonix)
*
* 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.
*/
/**
* Combinatorics utility functions.
*/
import { nestedMapSet, nestedMapHas, mapYield } from "./core";
import { arrayOrArrayLike } from "./array";
/**
* Yields all the combinations of an array without repetitions (binomial coefficient).
*
* @generator
* @param {Array} items An array.
* @param {number} numberOfItemsPerCombination The number of elements in each combination
* (this function assumes it to be less than "items.length"
* and greater than 0).
* @param {boolean} yieldCopy True if the yielded combination should be a copy (default) or
* the internal array used during the generation of the current combination.
* @yields {Array} The next combination.
*/
export const yieldCombinationsWithoutRepetition = function* (
items,
numberOfItemsPerCombination,
yieldCopy = true
) {
const l = items.length;
const prefix = [];
const recurse = function* recurse(
items,
l,
prefix,
numberOfItemsPerCombination,
nextIndex = 0
) {
// Same as:
// if (nextIndex === numberOfItemsPerCombination) {
if (prefix.length === numberOfItemsPerCombination) {
// Base case:
yield yieldCopy ? arrayOrArrayLike(prefix) : prefix;
} else {
// Recurrent case:
for (
let i = nextIndex;
i < l &&
// Remaining needed items
numberOfItemsPerCombination - prefix.length <=
// Remaining available items
l - i;
i++
) {
prefix.push(items[i]);
yield* recurse(items, l, prefix, numberOfItemsPerCombination, i + 1);
prefix.pop();
}
}
};
yield* recurse(items, l, prefix, numberOfItemsPerCombination);
};
/**
* Generate unique, progressive and incremental combinations.
*
* @param {Array} items An array of items.
* @return {Array[]} An array of arrays, each representing a unique progressive incremental combination.
* An empty array is returned if the given items array is empty.
*/
export const uniqueProgressiveIncrementalCombinations = items => {
const len = items.length;
if (len === 0) {
return [];
}
const last = arrayOrArrayLike(items); // Shallow copy/clone of the given array.
if (len === 1) {
// [1] => [[1]]
return [last];
}
const ret = [];
// [1], [2], [3], ..., [n]
items.map(item => ret.push([item]));
if (len > 2) {
// There are at least three items.
for (
let numberOfItemsPerCombination = 2;
numberOfItemsPerCombination < len;
numberOfItemsPerCombination++
) {
for (const combination of yieldCombinationsWithoutRepetition(
items,
numberOfItemsPerCombination
)) {
ret.push(combination);
}
}
}
ret.push(last);
return ret;
};
/**
* Yields unique, progressive and incremental combinations.
*
* @generator
* @param {Array} items An array of items.
* @param {boolean} yieldCopy True if some of the yielded combinations should be a copy (default)
* of the corresponding internal array used during the generation of the current combination
* or that same array should be returned (if "yieldCopy" is "false").
* @yields {Array} An array, each representing the next unique progressive incremental combination.
* An empty array is yielded if the given items array is empty.
*/
export const yieldUniqueProgressiveIncrementalCombinations = function* (
items,
yieldCopy = true
) {
const len = items.length;
if (len === 0) {
return;
}
const last = yieldCopy ? arrayOrArrayLike(items) : items; // Shallow copy/clone of the given array.
if (len === 1) {
// [1] => [[1]]
yield last;
return;
}
// [1], [2], [3], ..., [n]
yield* mapYield(items, item => [item]);
if (len > 2) {
// There are at least three items.
for (
let numberOfItemsPerCombination = 2;
numberOfItemsPerCombination < len;
numberOfItemsPerCombination++
) {
for (const combination of yieldCombinationsWithoutRepetition(
items,
numberOfItemsPerCombination,
yieldCopy
)) {
yield combination;
}
}
}
yield last;
};
/**
* Yields all the subsequences of the given array of items.
*
* @generator
* @param {Array} items An array of items to use to yield subsequences.
* @yields {Array} The next subsequence.
*/
export const yieldAllSubsequences = function* (items) {
const l = items.length;
for (let i = 0; i <= l; i++) {
for (const combination of yieldCombinationsWithoutRepetition(items, i)) {
yield combination;
}
}
};
/**
* Yields only the unique subsequences of the given array of items.
*
* @generator
* @param {Array} items An array of items to use to yield subsequences.
* @yields {Array} The next unique subsequence.
*/
export const yieldUniqueSubsequences = function* (items) {
const map = new Map();
yield [];
for (const subsequence of yieldAllSubsequences(items)) {
if (!nestedMapHas(map, subsequence)) {
nestedMapSet(map, subsequence, true);
yield subsequence;
}
}
};
/**
* Yields all the permutations of the given array of items.
*
* @generator
* @param {Array} items An array of items to use to yield permutations.
* @param {boolean} yieldCopy True if the yielded permutation should be a copy (default) or
* the internal array used during the generation of the current permutation.
* @yields {Array} The next permutation.
*/
export const yieldPermutations = function* (items, yieldCopy = true) {
const currentPermutationPrefix = [];
const currentPermutationIndicesMap = {};
const permute = function* () {
if (currentPermutationPrefix.length === items.length) {
yield yieldCopy
? arrayOrArrayLike(currentPermutationPrefix)
: currentPermutationPrefix;
} else {
for (let i = 0; i < items.length; i++) {
if (currentPermutationIndicesMap[i]) {
continue;
}
currentPermutationPrefix.push(items[i]);
currentPermutationIndicesMap[i] = true;
yield* permute();
delete currentPermutationIndicesMap[i];
currentPermutationPrefix.pop();
}
}
};
yield* permute();
};