jane st [2025-01-01 Wed]

solution

GCD
012345679
middle row (solution)
283950617

possible approaches

  • count down possible GCDs until one is found (picked this one)
    • should be able to start at some # around 100MM and count down
    • optimize for quickly ruling out that a GCD could be a solution
    • lots of solutions for smaller numbers that we get to not worry about
  • get valid sudoku solutions and then calculate gcds
    • so many solutions, so many more with 10 possible combinations of used digits

things I have determined

gcd must generate 9 multiples

  • must be distinct (otherwise doesn't solve column requirement of sudoku)
  • must be between 12345678 and 987654321
  • must not repeat digits
  • must use the same set of 9 digits

some high numbers with > 9 valid multiples to test against

num multiples
61728395 11
49382716 14
36984127 16
31759246 10
31745926 18

limits

987654321
max without repeating digits 987654321
109739369
987654321 / 9 max gcd that could produce 9 distinct row numbers
061728395
highest GCD to produce 9 distinct row numbers
014237856
highest GCD to product 9 distinct row numbers that use the same digits (no 9)
012345679
can place 2 rows
012345679
can place 3 rows
012345679
can place 9 rows

constants

const gridSize = 9;
const fixedGridValues = [
        [ null, null, null, null, null, null, null, '2', null],
        [ null, null, null, null, null, null, null, null, '5'],
        [ null, '2', null, null, null, null, null, null, null],
        [ null, null, '0', null, null, null, null, null, null],
        [ null, null, null, null, null, null, null, null, null],
        [ null, null, null, '2', null, null, null, null, null],
        [ null, null, null, null, '0', null, null, null, null],
        [ null, null, null, null, null, '2', null, null, null],
        [ null, null, null, null, null, null, '5', null, null],
];

// valid set can't be missing 0, 2, or 5 because of the given grid
const validSets = [
        new Set(['0',      '2', '3', '4', '5', '6', '7', '8', '9']),
        new Set(['0', '1', '2',      '4', '5', '6', '7', '8', '9']),
        new Set(['0', '1', '2', '3',      '5', '6', '7', '8', '9']),
        new Set(['0', '1', '2', '3', '4', '5',      '7', '8', '9']),
        new Set(['0', '1', '2', '3', '4', '5', '6',      '8', '9']),
        new Set(['0', '1', '2', '3', '4', '5', '6', '7',      '9']),
        new Set(['0', '1', '2', '3', '4', '5', '6', '7', '8'     ]),
];

let lowestPossibleNumber = 12345678;
let maxPossibleNumber =   987654321;
const experimentallyDeterminedStart = maxPossibleNumber / 9;

functions

const setsEqual = (s0, s1) => s0.size === s1.size && [...s0].every((x) => s1.has(x));
function main() {
        for (let i = experimentallyDeterminedStart; i > 0; i--) {
                // write with process.stdout so that we can see where we are but it is immediately overwritten
                process.stdout.write('\r' + i);
                const validMultiples = getMultiplesOfGCDWithoutRepeatingDigits(i)
                if (validMultiples.length > 8) {
                        console.log();
                        console.log(validMultiples);
                        console.log(i + ' has ' + validMultiples.length  + ' options');
                        const sortedValidMultiples = validMultiples
                                  .map(num => num.toString().padStart(9, '0'))
                                  .reduce((arr, m) => {
                                          const match = arr.find(e => setsEqual(e[0], new Set(m)));
                                          if (match !== undefined) {
                                                  match[1].push(m);
                                          }
                                          return arr;
                                  }, validSets.map(s => [s, []]));
                        console.log(sortedValidMultiples);
                        sortedValidMultiples.forEach(m => {
                                if (m[1].length > 8) {
                                        console.log(i + ' has ' + m  + ' keyed options');
                                        tryFillRows([], m[1], 0);
                                }
                        });
                }
        }
}

main();
function tryFillRows(grid, rowNumbersAvailable, placingRow) {
        let possibleGrids = [];
        // if (placingRow === 5) {
        //      console.log('got 4 rows');
        //      printSudokuGrid(grid);
        // }
        if (placingRow === 9) {
                printSudokuGrid(grid);
                process.exit();
                return [grid];
        }

        rowNumbersAvailable.forEach((rowOption, rowOptionIndex) => {
                let isValidPlacement = true;
                // check if conflicts with placed rows
                for (let column = 0; column < gridSize; column++) {
                        const myColumnChar = rowOption[column];
                        for (let row = 0; row < placingRow; row++) {
                                if (grid[row][column] === myColumnChar) {
                                        isValidPlacement = false;
                                        // console.log(grid);
                                        // console.log('cant place ' + rowOption);
                                }
                        }
                }

                // check if conflicts with fixed nums
                fixedGridValues[placingRow].forEach((fixedElement, fixedElementIndex) => {
                        if (fixedElement !== null) {
                                if (rowOption[fixedElementIndex] !== fixedElement) {
                                        isValidPlacement = false;
                                        // console.log(grid);
                                        // console.log('cant place ' + rowOption);
                                }
                        }
                });

                // TODO check if conflicts with placed rows
                // if placingRow % 3 == 2 check squares

                if (isValidPlacement) {
                        possibleGrids.concat(
                                [
                                        ...tryFillRows([...grid, rowOption.split("")], [...rowNumbersAvailable.slice(0, rowOptionIndex), ...rowNumbersAvailable.slice(rowOptionIndex + 1)], placingRow + 1)
                                ]
                        );
                }
        });
        return possibleGrids;
}
function getMultiplesOfGCDWithoutRepeatingDigits(gcd) {
        // row would have to be 8 or 9 digits b/c only 1 leading zero
        let usableMultiples = [];
        let multiple = Math.ceil(lowestPossibleNumber / gcd) * gcd;

        while (multiple < maxPossibleNumber) {
                if (!numberHasRepeatedDigits(multiple)){
                        usableMultiples.push(multiple);
                }
                multiple += gcd;
        }

        return usableMultiples;
}
function numberHasRepeatedDigits(num) {
        const str = num.toString();
        const digits = new Set(str);
        return digits.size !== str.length;
}
function printSudokuGrid(grid) {
        const separator = "+-------+-------+-------+";

        for (let i = 0; i < grid.length; i++) {
                if (i % 3 === 0) console.log(separator);

                const row = grid[i]
                        .map((val, idx) => (val === 0 ? "." : val)) // Use `.` for empty cells (0)
                        .reduce((acc, val, idx) => {
                                const groupSeparator = (idx + 1) % 3 === 0 ? " |" : "";
                                return acc + ` ${val}${groupSeparator}`;
                        }, "|");

                console.log(row);
        }
        console.log(separator);
}

solution grid

 3 9 5 
 0 6 1 
 7 2 8 
 0 6 1 
 7 2 8 
 3 9 5 
 7 2 8 
 3 9 5 
 0 6 1 
 9 5 0 
 2 8 3 
 6 1 7 
 6 1 7 
 9 5 0 
 2 8 3 
 2 8 3 
 6 1 7 
 9 5 0 
 8 3 9 
 5 0 6 
 1 7 2 
 5 0 6 
 1 7 2 
 8 3 9 
 1 7 2 
 8 3 9 
 5 0 6