json-schema-describes-subset
Version:
Tools for static JSON schema analysis, including functions to determine if one schema describes a subset of another or if a schema describes the empty set or to convert a schema to its disjunctive normal form (DNF).
181 lines • 5.13 kB
JavaScript
import uniq from 'lodash/uniq.js';
import { multipleOfEpsilon } from '../../../validate/index.js';
/**
* Returns whether the given value is (almost) zero.
*
*/
export function isCloseToZero(value) {
return Math.abs(value) < multipleOfEpsilon;
}
/**
* Returns the (rounded) value if it is (almost) an integer. Otherwise returns
* `null`.
*
*/
export function assertInteger(value) {
const rounded = Math.round(value);
return isCloseToZero(rounded - value) ? rounded : null;
}
export function isInteger(value) {
return assertInteger(value) !== null;
}
export function floor(value) {
return assertInteger(value) ?? Math.floor(value);
}
export function ceil(value) {
return assertInteger(value) ?? Math.ceil(value);
}
/**
* Finds the greatest common divisor using the Euclidean algorithm
*
* @param a A non negative finite integer
*
* @param b Another non negative finite integer
*
* @returns The greatest common divisor of a and b
*
*/
export function gcd(a, b) {
/* checking against a large epsilon is sufficient, because the arguments are
* supposed to be integers anyway */
if (b < 0.1) {
return a;
}
/* Use `Math.round` to prevent rounding errors introduced by `%` */
return gcd(b, Math.round(a % b));
}
/**
* Finds the least common multiple
*
* @param a A non negative integer
*
* @param b Another non negative integer
*
* @returns The least common multiple of a and b
*
*/
export function lcm(a, b) {
if (Number.isNaN(a) || Number.isNaN(b)) {
return NaN;
}
if (!Number.isFinite(a) || !Number.isFinite(b)) {
return Infinity;
}
/* checking against a large epsilon is sufficient, because the arguments are
* supposed to be integers anyway */
if (a < 0.1 || b < 0.1) {
return 0;
}
return Math.round((a * b) / gcd(Math.round(a), Math.round(b)));
}
/**
* @param a A non negative real number
*
* @param b Another non negative real number
*
* @returns The non negative least common multiple of a and b
*
*/
export function nonNegativeRealLCM(a, b) {
if (Number.isNaN(a) || Number.isNaN(b)) {
return NaN;
}
if (!Number.isFinite(a) || !Number.isFinite(b)) {
return Infinity;
}
if (isInteger(a) && isInteger(b)) {
return lcm(a, b);
}
return nonNegativeRealLCM(a * 10, b * 10) / 10;
}
/**
* @param a A real number
*
* @param b Another real number
*
* @returns The non negative least common multiple of a and b
*
*/
export function realLCM(a, b) {
return nonNegativeRealLCM(Math.abs(a), Math.abs(b));
}
export function multiRealLCM(...values) {
const [valueA, valueB, ...restValues] = values;
if (valueA === undefined) {
return undefined;
}
if (valueB === undefined) {
return valueA;
}
return multiRealLCM(realLCM(valueA, valueB), ...restValues);
}
export function isValidMultiple(multipleOf, minimum = -Infinity, maximum = Infinity) {
return function (value) {
if (typeof value !== 'number') {
return false;
}
if (value < minimum || value > maximum) {
return false;
}
if (multipleOf === 0) {
return value === 0;
}
return isInteger(value / multipleOf);
};
}
/**
* Finds the cardinality of
*
* ```
* { x |
* x ≥ `minimum` AND
* x ≤ `maximum` AND
* x is multiple of `multipleOf`
* }
* ```
*
*/
export function findMultipleOfCardinality(multipleOf, minimum, maximum) {
if (multipleOf === 0) {
if (minimum <= 0 && maximum >= 0) {
return { cardinality: 1, singleElement: 0 };
}
else {
return { cardinality: 0 };
}
}
const absMultipleOf = Math.abs(multipleOf);
const minFactor = ceil(minimum / absMultipleOf);
const maxFactor = floor(maximum / absMultipleOf);
const cardinality = Math.max(0, maxFactor - minFactor + 1);
if (cardinality === 1) {
return { cardinality, singleElement: minFactor * absMultipleOf };
}
return { cardinality };
}
/**
* Finds the cardinality of
*
* ```
* { x |
* x ≥ minimum AND
* x ≤ maximum AND
* (
* x is multiple of one of the values in multipleUnionOf OR
* x is one of the values in constUnion
* )
* }
* ```
*
*/
export function findMultipleOfUnionCardinality(minimum, maximum, multipleOfUnion, constUnion) {
const [multipleOf, ...otherMultipleOfs] = multipleOfUnion;
if (multipleOf === undefined) {
return uniq(constUnion).filter((constValue) => constValue >= minimum && constValue <= maximum).length;
}
// recursively apply |A ∪ B| = |A| + |B| - |A ∩ B|
return (findMultipleOfCardinality(multipleOf, minimum, maximum).cardinality +
findMultipleOfUnionCardinality(minimum, maximum, otherMultipleOfs, constUnion) -
findMultipleOfUnionCardinality(minimum, maximum, otherMultipleOfs.map((otherMultipleOf) => realLCM(otherMultipleOf, multipleOf)), constUnion.filter(isValidMultiple(multipleOf, minimum, maximum))));
}
//# sourceMappingURL=multiple-of-utils.js.map