Initializing help system before first use

Sudoku - Using the solution pool enumeration feature


Type: Sudoku
Rating: 3 (intermediate)
Description: Sudoku demonstrating solution enumeration.
File(s): Sudoku.java


Sudoku.java
// (c) 2026-2026 Fair Isaac Corporation
import java.util.Arrays;
import java.util.Collection;

import com.dashoptimization.ColumnType;
import com.dashoptimization.DefaultMessageListener;
import com.dashoptimization.maps.HashMap3;
import com.dashoptimization.objects.Variable;
import com.dashoptimization.objects.XpressProblem;
import static com.dashoptimization.objects.Utils.sum;

/** 9x9 Sudoku puzzle to illustrate solution enumeration.
 * This is a pure feasibility problem, i.e., the objective function is all-zero.
 * We first solve with prefixed values that allow exactly one solution. Then
 * we add clear one of the fixings and enumerate all solutions the new board
 * allows.
 */
public final class Sudoku {
  /** The available row indices (first row has index zero). */
  private static final Collection<Integer> ROWS = Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8);
  /** The available column indices (first column has index zero). */
  private static final Collection<Integer> COLS = Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8);
  /** The available values (1 through 9). */
  private static final Collection<Integer> VALS = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
  /** Values that are fixed in the beginning.
   * These fixings allow exactly one feasible solution.
   * Data set from "The Guardian", 15 July, 2005. http://www.guardian.co.uk/sudoku
   */
  private static final int[][] PRESET = {
    { 0, 0, 0, 7, 0, 5, 6, 9, 0 },
    { 0, 0, 8, 0, 4, 0, 0, 0, 7 },
    { 0, 2, 0, 0, 0, 3, 0, 0, 0 },
    { 0, 0, 2, 0, 0, 0, 9, 0, 0 },
    { 0, 0, 0, 9, 0, 0, 0, 8, 0 },
    { 0, 0, 0, 0, 1, 0, 0, 0, 5 },
    { 0, 0, 0, 0, 0, 4, 0, 0, 0 },
    { 0, 0, 6, 0, 0, 0, 3, 0, 4 },
    { 0, 0, 0, 8, 2, 1, 0, 5, 0 },
  };
  /** Solve the 9x9 Sudoku puzzle with the given fixed values.
   * @param fixed      The values that are fixed on the board.
   * @param enumerate  If <code>true</code> then we enumerate all solutions,
   *                   otherwise we stop on the first solution. Note that
   *                   enumeration can become expensive the more solutions
   *                   there are.
   */
  private static void solve(int[][] fixed, boolean enumerate) {
    try (XpressProblem prob = new XpressProblem()) {
      // Create the variables. One binary variable for each combination of
      // row, column, value.
      // We store the variables in a map rather than a 3D array since rows and
      // columns index from 0, but values index from 1.
      HashMap3<Integer,Integer,Integer,Variable> b = prob.addVariables(ROWS, COLS, VALS)
        .withName("b(%d,%d,%d)")
        .withType(ColumnType.Binary)
        .toMap();

      // Fix the variables that should be fixed on the initial board.
      for (int r : ROWS) {
        for (int c : COLS) {
          if (fixed[r][c] > 0) {
            b.get(r, c, fixed[r][c]).fix(1.0);
          }
        }
      }

      // Each cell must have a number.
      prob.addConstraints(ROWS, COLS, (r, c) -> sum(VALS, v -> b.get(r, c, v)).eq(1.0));

      // Each number must appear exactly once in a row.
      prob.addConstraints(ROWS, VALS, (r,v) -> sum(COLS, c -> b.get(r, c, v)).eq(1.0));
      // Each number must appear exactly once in a column.
      prob.addConstraints(COLS, VALS, (c,v) -> sum(ROWS, r -> b.get(r, c, v)).eq(1.0));
      // In 3x3 squares all values must be different.
      for (int k = 0; k < 3; ++k) {
        Collection<Integer> colidx = Arrays.asList(3 * k, 1 + 3 * k, 2 + 3 * k);
        for (int v : VALS) {
          prob.addConstraint(sum(Arrays.asList(0, 1, 2), colidx,
                                 (x, y) -> b.get(x, y, v)).eq(1.0));
          prob.addConstraint(sum(Arrays.asList(3, 4, 5), colidx,
                                 (x, y) -> b.get(x, y, v)).eq(1.0));
          prob.addConstraint(sum(Arrays.asList(6, 7, 8), colidx,
                                 (x, y) -> b.get(x, y, v)).eq(1.0));
        }
      }
      // Enable log output and prepare for numeration (if requested).
      prob.callbacks.addMessageCallback(DefaultMessageListener::console);
      if (enumerate) {
        prob.controls().setPoolEnumStrategy(com.dashoptimization.XPRSenumerations.PoolEnumStrategy.FULL);
        prob.controls().setPoolLimit(Integer.MAX_VALUE); // Find all solutions.
      }
      // Find all solutions.
      prob.optimize();
      // Print solutions.
      // Note that we only print the first 10 solutions to not flood the screen.
      for (int s = 0; s < Math.min(10, prob.attributes().getPoolSize()); ++s) {
        System.out.println("Solution " + s + ":");
        printSolution(b, prob.getPoolSolution(s));
      }
    }
  }

  /** Print a board to the console.
   * @param board  A 9x9 array that specifies the numbers that are set in
   *               board. A zero means "not set".
   */
  private static void printBoard(int[][] board) {
    for (int r = 0; r < board.length; ++r) {
      String fill = (r == 3 || r == 6) ? "===+" : "---+";
      System.out.print("+");
      for (int c = 0; c < board[r].length; ++c) {
        System.out.print(fill);
        if (c == 2 || c == 5)
          System.out.print('+');
      }
      System.out.println();
      System.out.print("|");
      for (int c = 0; c < board[r].length; ++c) {
        System.out.printf(" %c |", (board[r][c] == 0) ? ' ' : (char)(board[r][c] + '0'));
        if (c == 2 || c == 5)
          System.out.print('|');
      }
      System.out.println();
    }
    System.out.print("+");
    for (int c = 0; c < board[0].length; ++c) {
      System.out.print("---+");
      if (c == 2 || c == 5)
        System.out.print('+');
    }
    System.out.println();
  }

  /** Print a soolution.
   * Constructs a board from a solution and then prints it.
   * @param b    The binary variables.
   * @param sol  The solution values for <code>b</code>.
   */
  private static void printSolution(HashMap3<Integer,Integer,Integer,Variable> b, double[] sol) {
    int[][] result = new int[9][9];
    for (int r : ROWS) {
      for (int c : COLS) {
        for (int v : VALS) {
          if (b.get(r, c, v).getValue(sol) > 0.5) {
            result[r][c] = v;
          }
        }
      }
    }
    printBoard(result);
  }

  public static void main(String[] args) {
    // First solve with the preset board. This should allow exactly one solution.
    solve(PRESET, false);
    // Now remove some fixed values and enumerate all solutions.
    PRESET[0][3] = 0;
    solve(PRESET, true);
  }
}

© 2001-2026 Fair Isaac Corporation. All rights reserved. This documentation is the property of Fair Isaac Corporation (“FICO”). Receipt or possession of this documentation does not convey rights to disclose, reproduce, make derivative works, use, or allow others to use it except solely for internal evaluation purposes to determine whether to purchase a license to the software described in this documentation, or as otherwise set forth in a written software license agreement between you and FICO (or a FICO affiliate). Use of this documentation and the software described in it must conform strictly to the foregoing permitted uses, and no other use is permitted.