Skip to content

Commit

Permalink
Optimize CountingCircles by checking if values are forced by houses.
Browse files Browse the repository at this point in the history
Specifically, checking if there are required values which much be
contained in an exclusion group.

Also fix a performance bug by sorting the cells in the constructor
so that the exclusion group finder could work better.
  • Loading branch information
sigh committed Sep 15, 2024
1 parent 4c72f80 commit f992a8f
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 8 deletions.
6 changes: 6 additions & 0 deletions data/collections.js
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,12 @@ const EXAMPLES = [
input: '.CountingCircles~R2C1~R1C3~R2C3~R1C6~R1C8~R1C9~R4C9~R4C8~R3C6~R4C5~R5C5~R5C4~R6C4~R6C3~R5C2~R6C9~R7C9~R8C7~R9C7~R7C5~R8C3~R9C2~R8C1.Cage~11~R2C1~R3C1.Cage~11~R1C3~R2C3.Cage~26~R1C6~R1C7~R1C8~R1C9.Cage~16~R3C6~R3C5~R4C5.Cage~15~R3C9~R4C9~R4C8.Cage~14~R6C9~R7C9~R8C9.Cage~15~R9C7~R8C7.Cage~15~R6C4~R6C5~R7C5.Cage~13~R5C5~R5C4.Cage~16~R5C2~R5C3~R6C3.Cage~29~R8C1~R9C1~R9C2~R9C3~R8C3.WhiteDot~R2C5~R2C6.WhiteDot~R9C4~R9C5.WhiteDot~R2C8~R3C8.',
solution: '123495678758632941469718532631279485274851396895346217942587163517963824386124759',
},
{
name: 'Bubble Tornado',
src: 'https://logic-masters.de/Raetselportal/Raetsel/zeigen.php?id=000F99',
input: '.CountingCircles~R1C1~R1C2~R1C3~R1C4~R1C5~R1C6~R1C7~R1C8~R1C9~R3C8~R2C8~R2C7~R3C7~R2C6~R2C5~R2C4~R2C3~R2C2~R2C1~R3C2~R3C3~R3C4~R3C5~R3C6~R4C2~R4C3~R4C4~R4C5~R4C6~R4C7~R5C7~R5C8~R5C9~R5C6~R5C5~R6C1~R6C2~R6C3~R6C4~R8C3~R9C2~R8C4~R7C5~R7C6~R7C7.WhiteDot~R1C8~R1C9.WhiteDot~R3C5~R4C5.WhiteDot~R4C5~R5C5.WhiteDot~R8C3~R9C3.WhiteDot~R6C4~R7C4.WhiteDot~R3C7~R4C7.WhiteDot~R2C4~R2C5.WhiteDot~R3C2~R4C2.WhiteDot~R4C3~R5C3.WhiteDot~R5C7~R6C7.WhiteDot~R5C8~R5C9',
solution: '853192467942786351176435892265849713314257689789613524421578936638924175597361248',
},
{
name: 'Taxicab Archers',
src: 'https://logic-masters.de/Raetselportal/Raetsel/zeigen.php?id=0005W8',
Expand Down
51 changes: 43 additions & 8 deletions js/solver/handlers.js
Original file line number Diff line number Diff line change
Expand Up @@ -3162,6 +3162,12 @@ SudokuConstraintHandler.Indexing = class Indexing extends SudokuConstraintHandle

SudokuConstraintHandler.CountingCircles = class CountingCircles extends SudokuConstraintHandler {
constructor(cells) {
// Ensure that cells are sorted:
// - Makes sure that the constraint performance is independent of the sort
// order of the cells.
// - Required for exclusion grouping to work optimally.
cells = cells.slice();
cells.sort((a, b) => a - b);
super(cells);
}

Expand Down Expand Up @@ -3200,6 +3206,7 @@ SudokuConstraintHandler.CountingCircles = class CountingCircles extends SudokuCo
this._numValues = shape.numValues;

this._exclusionMap = new Uint16Array(this.cells.length);
this._exclusionGroups = exclusionGroups;
for (let i = 0; i < exclusionGroups.length; i++) {
for (const cell of exclusionGroups[i]) {
this._exclusionMap[this.cells.indexOf(cell)] = 1 << i;
Expand Down Expand Up @@ -3255,37 +3262,65 @@ SudokuConstraintHandler.CountingCircles = class CountingCircles extends SudokuCo
// Count each possible value and restrict cells.
const numValues = this._numValues;
const exclusionMap = this._exclusionMap;
for (let j = 1; j < numValues + 1; j++) {
// Iterate in reverse order as larger numbers are more constrained.
for (let j = numValues; j > 0; j--) {
const v = LookupTables.fromValue(j);
if (!(v & allowedValues)) continue;

let totalCount = 0;
let fixedCount = 0;
let exclusionGroups = 0;
let vExclusionGroups = 0;
for (let i = 0; i < numCells; i++) {
if (grid[cells[i]] & v) {
totalCount++;
fixedCount += (grid[cells[i]] === v);
exclusionGroups |= exclusionMap[i];
vExclusionGroups |= exclusionMap[i];
}
}
const numExclusionGroups = countOnes16bit(exclusionGroups);
const numExclusionGroups = countOnes16bit(vExclusionGroups);

if (fixedCount > j) {
// There are too many fixed values.
return false;
}
if (numExclusionGroups < j) {
// If there are too few exclusion groups, then we can't have this value.
// If the value is required, then this is a conflict.
if (v & requiredValues) {
return false;
} else {
for (let i = 0; i < numCells; i++) {
if (!(grid[cells[i]] &= ~v)) return false;
}
}
} else if (totalCount === j && (v & requiredValues & unfixedValues)) {
for (let i = 0; i < numCells; i++) {
if (grid[cells[i]] & v) {
grid[cells[i]] = v;
} else if (totalCount === j) {
// If we have the exact count and the value is required,
// then we can fix the values.
if (v & requiredValues & unfixedValues) {
for (let i = 0; i < numCells; i++) {
if (grid[cells[i]] & v) {
grid[cells[i]] = v;
}
}
}
} else if (numExclusionGroups === j && (v & requiredValues & unfixedValues)) {
// If there is an exact number of exclusion groups, then check if
// any have just a single cell and hence can be fixed.
while (vExclusionGroups) {
const vGroup = vExclusionGroups & -vExclusionGroups;
vExclusionGroups ^= vGroup;
const group = this._exclusionGroups[LookupTables.toIndex(vGroup)];
let uniqueCell = 0;
let count = 0;
for (let k = 0; k < group.length; k++) {
const cell = group[k];
if (grid[cell] & v) {
if (++count > 1) break;
uniqueCell = cell;
}
}
if (count === 1) {
grid[uniqueCell] = v;
}
}
}
Expand Down

0 comments on commit f992a8f

Please sign in to comment.