finitedomain
Version:
A fast feature rich finite domain solver
301 lines (247 loc) • 9.29 kB
JavaScript
// This file only concerns itself with adding two domains
// The algorithm is conceptually simple but the support
// for both array and numbered domains makes it a little
// bloated. However, since it saves significant we do it
// anyways.
// Conceptually: range1+range2 = [lo1+lo2, hi1+hi2]
// [5, 10] + [20, 30]
// [5+20, 10+30] -> [25, 40]
import {
SMALL_MAX_NUM,
SOLVED_FLAG,
SUP,
ASSERT,
ASSERT_NUMDOM,
ASSERT_NORDOM,
ASSERT_STRDOM,
} from '../helpers';
import {
EMPTY,
EMPTY_STR,
STR_RANGE_SIZE,
STR_VALUE_SIZE,
domain_str_closeGaps,
domain_num_createRange,
domain_str_decodeValue,
domain_str_encodeRange,
domain_max,
domain_str_simplify,
domain_toSmallest,
} from '../domain';
let MIN = Math.min;
// BODY_START
/**
* Does not harm input domains
*
* @param {$domain} domain1
* @param {$domain} domain2
* @returns {$domain}
*/
function domain_plus(domain1, domain2) {
ASSERT_NORDOM(domain1);
ASSERT_NORDOM(domain2);
// note: this is not 0+x=x. this is nothing+something=nothing because the domains contain no value
if (!domain1) return EMPTY;
if (!domain2) return EMPTY;
let isNum1 = typeof domain1 === 'number';
let isNum2 = typeof domain2 === 'number';
let result;
if (isNum1 && isNum2) {
// if the highest number in the result is below the max of a small
// domain we can take a fast path for it. this case happens often.
if (_domain_plusWillBeSmall(domain1, domain2)) {
return _domain_plusNumNumNum(domain1, domain2);
}
result = _domain_plusNumNumStr(domain1, domain2);
} else {
if (isNum1) result = _domain_plusNumStrStr(domain1, domain2);
else if (isNum2) result = _domain_plusNumStrStr(domain2, domain1); // swapped domains!
else result = _domain_plusStrStrStr(domain1, domain2);
}
return domain_toSmallest(domain_str_simplify(result));
}
function _domain_plusStrStrStr(domain1, domain2) {
ASSERT_STRDOM(domain1);
ASSERT_STRDOM(domain2);
// Simplify the domains by closing gaps since when we add
// the domains, the gaps will close according to the
// smallest interval width in the other domain.
let domains = domain_str_closeGaps(domain1, domain2);
domain1 = domains[0];
domain2 = domains[1];
let newDomain = EMPTY_STR;
for (let index = 0, len = domain1.length; index < len; index += STR_RANGE_SIZE) {
let lo = domain_str_decodeValue(domain1, index);
let hi = domain_str_decodeValue(domain1, index + STR_VALUE_SIZE);
newDomain += _domain_plusRangeStrStr(lo, hi, domain2);
}
return newDomain;
}
function _domain_plusWillBeSmall(domain1, domain2) {
// if both domains are small enough they cannot add to a domain beyond the max
ASSERT(typeof domain1 === 'number', 'ONLY_WITH_NUMBERS');
ASSERT(typeof domain2 === 'number', 'ONLY_WITH_NUMBERS');
//if (((domain1 | domain2) >>> 0) < (1 << 15)) return true; // could catch some cases
//if (domain1 < (1<<15) && domain2 < (1<<15)) return true; // alternative of above
return domain_max(domain1) + domain_max(domain2) <= SMALL_MAX_NUM; // if max changes, update above too!
}
function _domain_plusNumNumStr(domain1, domain2) {
ASSERT_NUMDOM(domain1);
ASSERT_NUMDOM(domain2);
if (domain1 >= SOLVED_FLAG) {
let solvedValue = domain1 ^ SOLVED_FLAG;
return _domain_plusRangeNumStr(solvedValue, solvedValue, domain2);
}
let flagIndex = 0;
// find the first set bit. must find something because small domain and not empty
while ((domain1 & (1 << flagIndex)) === 0) ++flagIndex;
let lo = flagIndex;
let hi = flagIndex;
let flagValue = 1 << ++flagIndex;
let newDomain = EMPTY_STR;
while (flagValue <= domain1 && flagIndex <= SMALL_MAX_NUM) {
if ((flagValue & domain1) > 0) {
if (hi !== flagIndex - 1) { // there's a gap so push prev range now
newDomain += _domain_plusRangeNumStr(lo, hi, domain2);
lo = flagIndex;
}
hi = flagIndex;
}
flagValue = 1 << ++flagIndex;
}
return newDomain + _domain_plusRangeNumStr(lo, hi, domain2);
}
function _domain_plusNumNumNum(domain1, domain2) {
ASSERT_NUMDOM(domain1);
ASSERT_NUMDOM(domain2);
ASSERT(domain1 !== EMPTY && domain2 !== EMPTY, 'SHOULD_BE_CHECKED_ELSEWHERE');
ASSERT(domain_max(domain1) + domain_max(domain2) <= SMALL_MAX_NUM, 'THE_POINTE');
if (domain1 >= SOLVED_FLAG) {
let solvedValue = domain1 ^ SOLVED_FLAG;
return _domain_plusRangeNumNum(solvedValue, solvedValue, domain2);
}
let flagIndex = 0;
// find the first set bit. must find something because small domain and not empty
while ((domain1 & (1 << flagIndex)) === 0) ++flagIndex;
let lo = flagIndex;
let hi = flagIndex;
let flagValue = 1 << ++flagIndex;
let newDomain = EMPTY;
while (flagValue <= domain1 && flagIndex <= SMALL_MAX_NUM) {
if ((flagValue & domain1) > 0) {
if (hi !== flagIndex - 1) { // there's a gap so push prev range now
newDomain |= _domain_plusRangeNumNum(lo, hi, domain2);
lo = flagIndex;
}
hi = flagIndex;
}
flagValue = 1 << ++flagIndex;
}
return newDomain | _domain_plusRangeNumNum(lo, hi, domain2);
}
function _domain_plusRangeNumNum(loi, hii, domain_num) {
ASSERT_NUMDOM(domain_num);
ASSERT(domain_num !== EMPTY, 'SHOULD_BE_CHECKED_ELSEWHERE');
if (domain_num >= SOLVED_FLAG) {
let solvedValue = domain_num ^ SOLVED_FLAG;
return _domain_plusRangeRangeNum(loi, hii, solvedValue, solvedValue);
}
let flagIndex = 0;
// find the first set bit. must find something because small domain and not empty
while ((domain_num & (1 << flagIndex)) === 0) ++flagIndex;
let lo = flagIndex;
let hi = flagIndex;
let flagValue = 1 << ++flagIndex;
let newDomain = EMPTY;
while (flagValue <= domain_num && flagIndex <= SMALL_MAX_NUM) {
if ((flagValue & domain_num) > 0) {
if (hi !== flagIndex - 1) { // there's a gap so push prev range now
newDomain |= _domain_plusRangeRangeNum(loi, hii, lo, hi);
lo = flagIndex;
}
hi = flagIndex;
}
flagValue = 1 << ++flagIndex;
}
return newDomain | _domain_plusRangeRangeNum(loi, hii, lo, hi);
}
function _domain_plusNumStrStr(domain_num, domain_str) {
ASSERT_NUMDOM(domain_num);
ASSERT_STRDOM(domain_str);
if (domain_num >= SOLVED_FLAG) {
let solvedValue = domain_num ^ SOLVED_FLAG;
return _domain_plusRangeStrStr(solvedValue, solvedValue, domain_str);
}
let flagIndex = 0;
// find the first set bit. must find something because small domain and not empty
while ((domain_num & (1 << flagIndex)) === 0) ++flagIndex;
let lo = flagIndex;
let hi = flagIndex;
let flagValue = 1 << ++flagIndex;
let newDomain = EMPTY_STR;
while (flagValue <= domain_num && flagIndex <= SMALL_MAX_NUM) {
if ((flagValue & domain_num) > 0) {
if (hi !== flagIndex - 1) { // there's a gap so push prev range now
newDomain += _domain_plusRangeStrStr(lo, hi, domain_str);
lo = flagIndex;
}
hi = flagIndex;
}
flagValue = 1 << ++flagIndex;
}
return newDomain + _domain_plusRangeStrStr(lo, hi, domain_str);
}
function _domain_plusRangeNumStr(loi, hii, domain_num) {
ASSERT_NUMDOM(domain_num);
if (domain_num >= SOLVED_FLAG) {
let solvedValue = domain_num ^ SOLVED_FLAG;
return _domain_plusRangeRangeStr(loi, hii, solvedValue, solvedValue);
}
let flagIndex = 0;
// find the first set bit. must find something because small domain and not empty
while ((domain_num & (1 << flagIndex)) === 0) ++flagIndex;
let lo = flagIndex;
let hi = flagIndex;
let flagValue = 1 << ++flagIndex;
let newDomain = EMPTY_STR;
while (flagValue <= domain_num && flagIndex <= SMALL_MAX_NUM) {
if ((flagValue & domain_num) > 0) {
if (hi !== flagIndex - 1) { // there's a gap so push prev range now
newDomain += _domain_plusRangeRangeStr(loi, hii, lo, hi);
lo = flagIndex;
}
hi = flagIndex;
}
flagValue = 1 << ++flagIndex;
}
return newDomain + _domain_plusRangeRangeStr(loi, hii, lo, hi);
}
function _domain_plusRangeStrStr(loi, hii, domain_str) {
ASSERT_STRDOM(domain_str);
let newDomain = EMPTY_STR;
for (let index = 0, len = domain_str.length; index < len; index += STR_RANGE_SIZE) {
let lo = domain_str_decodeValue(domain_str, index);
let hi = domain_str_decodeValue(domain_str, index + STR_VALUE_SIZE);
newDomain += _domain_plusRangeRangeStr(loi, hii, lo, hi);
}
return newDomain;
}
function _domain_plusRangeRangeStr(loi, hii, loj, hij) {
ASSERT(loi + loj >= 0, 'DOMAINS_SHOULD_NOT_HAVE_NEGATIVES');
let lo = loi + loj;
if (lo <= SUP) { // if lo exceeds SUP the resulting range is completely OOB and we ignore it.
let hi = MIN(SUP, hii + hij);
return domain_str_encodeRange(lo, hi);
}
return EMPTY_STR;
}
function _domain_plusRangeRangeNum(loi, hii, loj, hij) {
ASSERT(loi + loj >= 0, 'DOMAINS_SHOULD_NOT_HAVE_NEGATIVES');
ASSERT(loi + loj <= SMALL_MAX_NUM, 'RESULT_SHOULD_NOT_EXCEED_SMALL_DOMAIN');
ASSERT(hii + hij <= SMALL_MAX_NUM, 'RESULT_SHOULD_NOT_EXCEED_SMALL_DOMAIN');
let domain = domain_num_createRange(loi + loj, hii + hij);
ASSERT(typeof domain === 'number' && domain < SOLVED_FLAG, 'expecting numdom, not soldom');
return domain;
}
// BODY_STOP
export default domain_plus;