finitedomain
Version:
A fast feature rich finite domain solver
278 lines (256 loc) • 8.55 kB
JavaScript
import expect from '../fixtures/mocha_proxy.fixt';
import {
domain__debug,
domain_anyToSmallest,
domain_toSmallest,
domain_arrToSmallest,
domain_toArr,
domain_toStr,
} from '../../src/domain';
const SUB = 0;
const SUP = 100000000;
const SMALL_MAX_NUM = 30;
const SOLVED_FLAG = 1 << 31 >>> 0;
function fixt_arrdom_range(lo, hi) {
if (arguments.length !== 2) throw new Error('fixme');
if (typeof lo !== 'number') {
throw new Error('specDomainCreateValue requires a number');
}
if (typeof hi !== 'number') {
throw new Error('specDomainCreateValue requires a number');
}
return [lo, hi];
}
function fixt_arrdom_ranges(...ranges) {
let arr = [];
ranges.forEach(function(range) {
if (!(range instanceof Array)) {
throw new Error('Expecting each range to be an array');
}
if (range.length !== 2) {
throw new Error('Expecting each range to be [lo,hi]');
}
if (typeof range[0] !== 'number') {
throw new Error('Expecting ranges to be numbers');
}
if (typeof range[1] !== 'number') {
throw new Error('Expecting ranges to be numbers');
}
return arr.push(range[0], range[1]);
});
if (arr[0] >= 0 && arr[arr.length-1] <= SMALL_MAX_NUM) throw new Error('NEED_TO_UPDATE_TO_SMALL_DOMAIN ['+arr+']');
// hack. makes sure the DOMAIN_CHECK test doesnt trigger a fail for adding that property...
return arr;
}
function fixt_arrdom_solved(value) {
if (arguments.length !== 1) throw new Error('fixme');
if (typeof value !== 'number') {
throw new Error('specDomainCreateValue requires a number');
}
return fixt_arrdom_range(value, value);
}
function fixt_arrdom_empty(no) {
let A = [];
if (!no) A.__skipEmptyCheck = true; // circumvents certain protections
return A;
}
function fixt_arrdom_nums(...list) {
if (!list.length) return [];
list.sort((a, b) => a - b); // note: default sort is lexicographic!
let domain = [];
let hi;
let lo;
for (let index = 0; index < list.length; index++) {
let value = list[index];
ASSERT(typeof value === 'number', 'fd values are numbers');
ASSERT(value >= SUB, 'fd values range SUB~SUP');
ASSERT(value <= SUP, 'fd values range SUB~SUP');
if (index === 0) {
lo = value;
hi = value;
} else {
ASSERT(value >= hi, 'LIST_SHOULD_BE_ORDERED_BY_NOW'); // imo it should not even contain dupe elements... but that may happen anyways
if (value > hi + 1) {
domain.push(lo, hi);
lo = value;
}
hi = value;
}
}
domain.push(lo, hi);
return domain;
}
function fixt_numdom_nums(...values) {
let d = 0;
for (let i = 0; i < values.length; ++i) {
if (typeof values[i] !== 'number') throw new Error('EXPECTING_NUMBERS_ONLY ['+values[i]+']');
if (values[i] < 0 || values[i] > SMALL_MAX_NUM) throw new Error('EXPECTING_SMALL_DOMAIN_VALUES ['+values[i]+']');
d |= 1 << values[i];
}
return d;
}
function fixt_numdom_range(lo, hi, _b) {
if (_b !== undefined) throw new Error('BAD_THIRD_ARG');
if (typeof lo !== 'number') throw new Error('LO_MUST_BE_NUMBER');
if (typeof hi !== 'number') throw new Error('HI_MUST_BE_NUMBER');
if (lo < 0 || hi > SMALL_MAX_NUM) throw new Error('OOB_FOR_SMALL_DOMAIN ['+lo+','+hi+']');
let d = 0;
for (; lo <= hi; ++lo) {
d |= 1 << lo;
}
return d;
}
function fixt_numdom_ranges(...ranges) {
return ranges.reduce((domain, range) => domain | fixt_numdom_range(...range), 0);
}
function fixt_numdom_empty() {
return 0; // magic value yo. no flags means zero
}
function fixt_numdom_solved(num) {
if (arguments.length !== 1) throw new Error('INVALID_PARAM_FIX_TEST');
if (num < SUB || num > SUP) throw new Error('SOLVED_NUM_MUST_BE_SUBSUP_BOUND');
return (num | SOLVED_FLAG) >>> 0; // num|flag could lead to negative value without the >>>
}
function fixt_strdom_empty() {
return '';
}
function fixt_strdom_value(value) {
return String.fromCharCode((value >>> 16) & 0xffff, value & 0xffff, (value >>> 16) & 0xffff, value & 0xffff);
}
function fixt_strdom_range(lo, hi) {
return String.fromCharCode((lo >>> 16) & 0xffff, lo & 0xffff, (hi >>> 16) & 0xffff, hi & 0xffff);
}
function fixt_strdom_ranges(...ranges) {
return ranges.map(([lo,hi]) => fixt_strdom_range(lo, hi)).join('');
}
function fixt_strdom_nums(...nums) {
if (!nums.length) return '';
nums.sort((a, b) => a - b); // note: default sort is lexicographic!
let s = '';
let lo;
let hi;
for (let i = 0; i < nums.length; ++i) {
if (i === 0) {
lo = hi = nums[i];
} else if (nums[i] === hi + 1) {
hi = nums[i];
} else if (nums[i] > hi + 1) {
s += fixt_strdom_range(lo, hi);
lo = hi = nums[i];
} else {
throw new Error('NUMS_SHOULD_BE_UNIQUE [' + nums + ']');
}
}
s += fixt_strdom_range(lo, hi);
return s;
}
function fixt_bytes(str, desc) {
expect(typeof str, desc).to.eql('string');
return [].map.call(str, s => s.charCodeAt(0)).join(', ');
}
function fixt_dom_empty() {
return 0;
}
function fixt_dom_range(lo, hi) {
if (arguments.length !== 2) throw new Error('Bad arg count');
if (typeof lo !== 'number') throw new Error('lo must be number');
if (typeof hi !== 'number') throw new Error('hi must be number');
if (!(lo <= hi)) throw new Error('should be lo <= hi');
if (lo === hi) return fixt_numdom_solved(lo);
if (hi <= SMALL_MAX_NUM) return fixt_numdom_range(lo, hi);
return fixt_strdom_range(lo, hi);
}
function fixt_dom_ranges(...ranges) {
if (ranges.length === 0) throw new Error('No ranges? Probably test bug');
if (ranges.length === 1 && ranges[0][0] === ranges[0][1]) return fixt_numdom_solved(ranges[0][0]);
if (ranges[ranges.length-1][1] <= SMALL_MAX_NUM) return fixt_numdom_ranges(...ranges);
return fixt_strdom_ranges(...ranges);
}
function fixt_dom_nums(...nums) {
if (nums[0] instanceof Array) throw new Error('you forgot to splat the argument');
nums.sort((a, b) => a - b);
if (nums.length === 0) throw new Error('No nums? Probably test bug');
if (nums.length === 1) return fixt_numdom_solved(nums[0]);
if (nums[nums.length - 1] <= SMALL_MAX_NUM) return fixt_numdom_nums(...nums);
return fixt_strdom_nums(...nums);
}
function fixt_dom_solved(value) {
if (typeof value !== 'number') throw new Error('Bad arg');
if (arguments.length !== 1) throw new Error('Bad arg');
return fixt_numdom_solved(value);
}
function stripAnonVars(solution) {
for (let name in solution) {
if ('__'+String(parseFloat(name.slice(2)))+'__' === name) { // only true of name is a number
delete solution[name];
}
}
return solution;
}
function stripAnonVarsFromArrays(solutions) {
for (let i = 0; i < solutions.length; i++) {
let solution = solutions[i];
stripAnonVars(solution);
}
return solutions;
}
function ASSERT(b, d) {
if (!b) throw new Error(d);
}
function fixt_assertStrings(a, b, desc) {
expect(fixt_bytes(a, desc), desc).to.eql(fixt_bytes(b, desc));
}
/**
* Assert two domains to be equal, regardless of their individual
* representation (numdom, soldom, strdom, arrdom). This helps to
* keep integration tests clean from unit tests that check for
* normalization of function return values. For example the
* propagators should just check the result value, not whether it
* returns a soldom in certain edge cases but not others.
*
* @param {$domain} result
* @param {$domain} expectation
* @param {string} [desc]
*/
function fixt_domainEql(result, expectation, desc) {
desc = `${desc || ''} comparing but ignoring representation; result: ${domain__debug(result)} expected: ${domain__debug(expectation)}`;
expect(domain_anyToSmallest(result), desc).to.eql(domain_anyToSmallest(expectation));
}
/**
* @param {$domain} domain
* @param {string} [force] Always return in array or string form?
* @returns {$domain}
*/
function fixt_dom_clone(domain, force) {
if (force === 'array') return domain_toArr(domain, true);
if (force === 'string') return domain_toStr(domain);
return domain;
}
export {
fixt_arrdom_empty,
fixt_arrdom_range,
fixt_arrdom_ranges,
fixt_arrdom_solved,
fixt_arrdom_nums,
fixt_dom_clone,
fixt_dom_empty,
fixt_dom_nums,
fixt_dom_range,
fixt_dom_ranges,
fixt_dom_solved,
fixt_assertStrings,
fixt_bytes,
fixt_domainEql,
fixt_numdom_empty,
fixt_numdom_nums,
fixt_numdom_range,
fixt_numdom_ranges,
fixt_numdom_solved,
fixt_strdom_empty,
fixt_strdom_value,
fixt_strdom_range,
fixt_strdom_ranges,
fixt_strdom_nums,
stripAnonVars,
stripAnonVarsFromArrays,
};