UNPKG

finitedomain

Version:

A fast feature rich finite domain solver

222 lines (195 loc) 8.1 kB
// Finite-Domain Helpers // Note: this file is post processed to remove the ASSERTs // A grunt cli (`grunt string-replace:perf`, which is also triggered // in `grunt perf`) will replace all lines that start with `ASSERT` // with a `1`, which acts as a noop to prevent syntax errors for // sub-statements (like condiditions). Additionally, there is a macro // `__REMOVE_BELOW_FOR_DIST__` and `__REMOVE_ABOVE_FOR_DIST__` which // act like barriers. Anything in between is removed and replaced with // an `x` so the result is `x=1` (just easier than the clean version). // We need to wipe these lines because we won't use them and when we // strip the ASSERT lines, syntax errors would happen in this file. // The export is preserved so the constants are still exported but // the method exports are stripped with the ASSERT replacement... // BODY_START let SUB = 0; // WARNING: adjusting SUB to something negative means adjusting all tests. probably required for any change actually. let SUP = 100000000; let SOLVED = 1; let UNDETERMINED = 0; let NOT_FOUND = -1; let LOG_NONE = 0; let LOG_STATS = 1; let LOG_SOLVES = 2; let LOG_MIN = LOG_NONE; let LOG_MAX = LOG_SOLVES; // different from NOT_FOUND in that NOT_FOUND must be -1 because of the indexOf api // while NO_SUCH_VALUE must be a value that cannot be a legal domain value (<SUB or >SUP) let NO_SUCH_VALUE = Math.min(0, SUB) - 1; // make sure NO_SUCH_VALUE is a value that may be neither valid in a domain nor >=0 let ENABLED = true; // override for most tests (but not regular ASSERTs) like full domains and space validations let ENABLE_DOMAIN_CHECK = false; // also causes unrelated errors because mocha sees the expandos let ENABLE_EMPTY_CHECK = false; // also causes unrelated errors because mocha sees the expandos let ARR_RANGE_SIZE = 2; const SMALL_MAX_NUM = 30; // there are SMALL_MAX_NUM flags. if they are all on, this is the number value // (oh and; 1<<31 is negative. >>>0 makes it unsigned. this is why 30 is max.) const SOLVED_FLAG = 1 << 31 >>> 0; // the >>> makes it unsigned, we dont really need it but it may help perf a little (unsigned vs signed) // __REMOVE_BELOW_FOR_ASSERTS__ ASSERT(SMALL_MAX_NUM <= 30, 'cant be larger because then shifting fails above and elsewhere'); ASSERT(NOT_FOUND === NO_SUCH_VALUE, 'keep not found constants equal to prevent confusion bugs'); // For unit tests // Should be removed in production. Obviously. function ASSERT(bool, msg = '', ...args) { if (bool) { return; } if (!msg) msg = new Error('trace').stack; console.error(`Assertion fail: ${msg}`); if (args) { console.log('Error args:', args); } // console.trace() // process.exit() # uncomment for quick error access :) let suffix = ''; if (args && args.length) { suffix = `Args (${args.length}x): \`${_stringify(args)}\``; } THROW(`Assertion fail: ${msg} ${suffix}`); } function _stringify(o) { if (o instanceof Array) { return `[ ${o.map(e => _stringify(e)).join(', ')} ]`; } return `${o}`; } // Simple function to completely validate a domain // Should be removed in production. Obviously. function ASSERT_STRDOM(domain, expectSmallest, domain__debug) { let s = domain__debug && domain__debug(domain); const strdomValueLen = 2; const strdomRangeLen = 2 * strdomValueLen; ASSERT(typeof domain === 'string', 'ONLY_STRDOM', s); ASSERT((domain.length % strdomRangeLen) === 0, 'SHOULD_CONTAIN_RANGES', s); let lo = (domain.charCodeAt(0) << 16) | domain.charCodeAt(1); let hi = (domain.charCodeAt(domain.length - strdomValueLen) << 16) | domain.charCodeAt(domain.length - strdomValueLen + 1); ASSERT(lo >= SUB, 'SHOULD_BE_GTE ' + SUB, s); ASSERT(hi <= SUP, 'SHOULD_BE_LTE ' + SUP, s); ASSERT(!expectSmallest || lo !== hi || domain.length > strdomRangeLen, 'SHOULD_NOT_BE_SOLVED', s); return true; } function ASSERT_SOLDOM(domain, value) { ASSERT(typeof domain === 'number', 'ONLY_SOLDOM'); ASSERT(domain >= 0, 'ALL_SOLDOMS_SHOULD_BE_UNSIGNED'); ASSERT(domain >= SOLVED_FLAG, 'SOLDOMS_MUST_HAVE_FLAG_SET'); ASSERT((domain ^ SOLVED_FLAG) >= SUB, 'SOLVED_NUMDOM_SHOULD_BE_MIN_SUB'); ASSERT((domain ^ SOLVED_FLAG) <= SUP, 'SOLVED_NUMDOM_SHOULD_BE_MAX_SUP'); if (value !== undefined) ASSERT((domain ^ SOLVED_FLAG) === value, 'SHOULD_BE_SOLVED_TO:' + value); return true; } function ASSERT_BITDOM(domain) { ASSERT(typeof domain === 'number', 'ONLY_BITDOM'); ASSERT(domain >= 0, 'ALL_BITDOMS_SHOULD_BE_UNSIGNED'); ASSERT(domain < SOLVED_FLAG, 'SOLVED_FLAG_NOT_SET'); ASSERT(SMALL_MAX_NUM < 31, 'next assertion relies on this'); ASSERT(domain >= 0 && domain < ((1 << (SMALL_MAX_NUM + 1)) >>> 0), 'NUMDOM_SHOULD_BE_VALID_RANGE'); return true; } function ASSERT_ARRDOM(domain, min, max) { ASSERT(domain instanceof Array, 'ONLY_ARRDOM'); if (domain.length === 0) return; ASSERT(domain.length % 2 === 0, 'SHOULD_CONTAIN_RANGES'); ASSERT(domain[0] >= (min || SUB), 'SHOULD_BE_GTE ' + (min || SUB)); ASSERT(domain[domain.length - 1] <= (max === undefined ? SUP : max), 'SHOULD_BE_LTE ' + (max === undefined ? SUP : max)); return true; } function ASSERT_NORDOM(domain, expectSmallest, domain__debug) { let s = domain__debug && domain__debug(domain); ASSERT(typeof domain === 'string' || typeof domain === 'number', 'ONLY_NORDOM', s); if (typeof domain === 'string') { ASSERT(domain.length > 0, 'empty domains are always numdoms'); if (expectSmallest) { let lo = (domain.charCodeAt(0) << 16) | domain.charCodeAt(1); let hi = ((domain.charCodeAt(domain.length - 2) << 16) | domain.charCodeAt(domain.length - 1)); ASSERT(hi > SMALL_MAX_NUM, 'EXPECTING_STRDOM_TO_HAVE_NUMS_GT_BITDOM', s); ASSERT(domain.length > 4 || lo !== hi, 'EXPECTING_STRDOM_NOT_TO_BE_SOLVED'); } return ASSERT_STRDOM(domain, undefined, undefined, s); } if (expectSmallest) ASSERT(!domain || domain >= SOLVED_FLAG || (domain & (domain - 1)) !== 0, 'EXPECTING_SOLVED_NUMDOM_TO_BE_SOLDOM', s); ASSERT_NUMDOM(domain, s); return true; } function ASSERT_NUMDOM(domain, expectSmallest, domain__debug) { let s = domain__debug && domain__debug(domain); ASSERT(typeof domain === 'number', 'ONLY_NUMDOM', s); if (expectSmallest) ASSERT(!domain || domain >= SOLVED_FLAG || (domain & (domain - 1)) !== 0, 'EXPECTING_SOLVED_NUMDOM_TO_BE_SOLDOM', s); if (domain >= SOLVED_FLAG) ASSERT_SOLDOM(domain); else ASSERT_BITDOM(domain); return true; } function ASSERT_ANYDOM(domain) { ASSERT(typeof domain === 'string' || typeof domain === 'number' || domain instanceof Array, 'ONLY_VALID_DOM_TYPE'); } function ASSERT_VARDOMS_SLOW(vardoms, domain__debug) { for (let varIndex = 0, len = vardoms.length; varIndex < len; varIndex++) { let domain = vardoms[varIndex]; ASSERT_NORDOM(domain, true, domain__debug); } } const LOG_FLAG_NONE = 0; const LOG_FLAG_PROPSTEPS = 1; const LOG_FLAG_CHOICE = 2; let LOG_FLAGS = LOG_FLAG_NONE; function ASSERT_SET_LOG(level) { LOG_FLAGS = level; } let helper_logger = (console => function() { console.log('LOG', ...arguments); })(console); function ASSERT_LOG(flags, func) { if (flags & LOG_FLAGS) { ASSERT(typeof func === 'function'); func(helper_logger); } } // __REMOVE_ABOVE_FOR_ASSERTS__ // Abstraction for throwing because throw statements cause deoptimizations // All explicit throws should use this function. Also helps with tooling // later, catching and reporting explicits throws and what not. function THROW(...msg) { throw new Error(msg.join(': ')); } // BODY_STOP export { // __REMOVE_BELOW_FOR_DIST__ ENABLED, ENABLE_DOMAIN_CHECK, ENABLE_EMPTY_CHECK, // __REMOVE_ABOVE_FOR_DIST__ LOG_FLAG_CHOICE, LOG_FLAG_NONE, LOG_FLAG_PROPSTEPS, LOG_NONE, LOG_STATS, LOG_SOLVES, LOG_MAX, LOG_MIN, NOT_FOUND, NO_SUCH_VALUE, ARR_RANGE_SIZE, SMALL_MAX_NUM, SOLVED, SOLVED_FLAG, SUB, SUP, UNDETERMINED, ASSERT, ASSERT_ANYDOM, ASSERT_ARRDOM, ASSERT_BITDOM, ASSERT_LOG, ASSERT_NORDOM, ASSERT_NUMDOM, ASSERT_SET_LOG, ASSERT_SOLDOM, ASSERT_STRDOM, ASSERT_VARDOMS_SLOW, THROW, };