Advertisement
Guest User

CrossNum.java

a guest
Jul 30th, 2014
202
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Java 11.95 KB | None | 0 0
  1. package crossnum;
  2.  
  3. import java.util.ArrayList;
  4. import java.util.Collections;
  5. import java.util.List;
  6.  
  7. /**
  8.  * Solve TarfHead's puzzle.
  9.  *
  10.  * Represents the problem as a set of simultaneous equations represented by Tuples,
  11.  * and uses a backtracking / constraint propagation algorithm to solve it.
  12.  *
  13.  * http://www.askaboutmoney.com/showthread.php?t=188279
  14.  *
  15.  *
  16.  * @author dub_nerd
  17.  *
  18.  */
  19. public class CrossNum {
  20.  
  21.     /**
  22.      * Main entry point
  23.      * @param args not used
  24.      */
  25.     public static void main(String[] args) {
  26.         (new CrossNum()).solve();
  27.     }
  28.  
  29.     /**
  30.      * A string representation of the board. This is just an easy way to type it
  31.      * in -- we'll immediately convert to an easier-to-use representation for
  32.      * solving using the Board class.
  33.      */
  34.     String strBoard =
  35.             "23 45 291 " +
  36.             "3 87  2 38" +
  37.             " 654 74  9" +
  38.             "2 578 64 1" +
  39.             " 29  6 891" +
  40.             "743 21 55 " +
  41.             " 9811 28  " +
  42.             "62 7 52 28" +
  43.             "9   841313" +
  44.             "2 3987  44" ;
  45.    
  46.     /**
  47.      * The row totals.
  48.      */
  49.     int rowTotals[] = {
  50.         47,41,51,45,51,35,55,43,47,52
  51.     };
  52.    
  53.     /**
  54.      * The column totals
  55.      */
  56.     int colTotals[] = {
  57.         44,40,54,51,52,46,36,56,34,54  
  58.     };
  59.    
  60.     /**
  61.      * Diagonal top left to bottom right.
  62.      */
  63.     int leadingDiagTotal = 32;
  64.  
  65.     /**
  66.      * Diagonal top right to bottom left.
  67.      */
  68.     int minorDiagTotal = 43;
  69.  
  70.     /**
  71.      * Guts of the algorithm.
  72.      */
  73.     void solve() {
  74.        
  75.         /*
  76.          * Convert initial board to a more wieldy representation.
  77.          */
  78.         Board board = new Board(strBoard);
  79.                
  80.         /*
  81.          * Set up the unknowns in the rows, columns, diagonals (i.e. tuples)
  82.          * as a set of simultaneous equations in an augmented matrix containing
  83.          * unknowns and required sums.
  84.          */
  85.         List<Tuple> augmentedMatrix = getAugmentedMatrix(board);
  86.        
  87.         /*
  88.          * Cache all numbers of combinations of up to 4 digits that sum to anything
  89.          * up to 30. For instance, combos.getNumberCombos(3, 14) would return the
  90.          * number of combinations of 3 digit numbers that sum to 14 (excluding all
  91.          * combinations that contain any zeros).
  92.          */
  93.         Combos combos = new Combos(4, 30);
  94.        
  95.         // Try column 4 ... to try all tuples restore the commented out values
  96.        
  97.         for (int i = /* 0 */ 13; i < /*augmentedMatrix.size()*/ 14; i++) {
  98.             totalCombosDone = 0;
  99.             boolean b =constrain(combos, augmentedMatrix, i);
  100.             System.out.printf("Start at %s, solved=%s, combos tried=%s\n", getTupleName(i), b, totalCombosDone);
  101.             // Print the unknown values (now known!)
  102.             for (int row = 0; row < board.getSide(); row++) {
  103.                 Tuple tuple = augmentedMatrix.get(row);
  104.                 boolean first = true;
  105.                 for (Variable v : tuple.variables()) {
  106.                     System.out.printf("%s", first? "  " : ", ");
  107.                     System.out.printf("B%s = %s", v.getOrdinal(), v.getValue());
  108.                     first = false;
  109.                 }
  110.                 System.out.println();
  111.             }
  112.         }
  113.  
  114.  
  115.     }
  116.  
  117.     /**
  118.      * Set up the augmented matrix
  119.      * @param board
  120.      * @return the augmented matrix as a list of tuples
  121.      */
  122.     private List<Tuple> getAugmentedMatrix(Board board) {
  123.        
  124.         List<Tuple> augmentedMatrix = new ArrayList<Tuple>();
  125.  
  126.         // Board's side length
  127.         int side = board.getSide();
  128.        
  129.         // We label our unknowns with an ordinal value starting at 1.
  130.         int ordinal = 1;
  131.        
  132.         /*
  133.          * Do rows and columns -- "isRow" indicates whether we are doing rows or
  134.          * columns. For each row and column we set up a tuple consisting of the
  135.          * unknown (i.e. = zero) values. We also set the tuple's required total
  136.          * by subtracting the known values from the totals given in the puzzle.
  137.          *
  138.          * We set an ordinal (label) for each unknown in ascending sequence.
  139.          * This is only done on the rows since we also encounter the same
  140.          * unknowns on the columns and diagonals. On each tuple we sort the
  141.          * unknown in ascending order of ordinal -- this is just a convenience
  142.          * so that when printing them out we see a consistent order.
  143.          */
  144.         for (boolean isRow : new boolean[] { true, false }) {
  145.  
  146.             for (int i = 0; i < side; i++) {
  147.                
  148.                 Tuple amRow = new Tuple();
  149.                 augmentedMatrix.add(amRow);
  150.                 int tupleActualTotal = 0;
  151.                 int tupleTotals[] = isRow? rowTotals : colTotals;
  152.                
  153.                 for (int j = 0; j < side; j++) {
  154.                    
  155.                     int row = isRow? i : j;
  156.                     int col = isRow? j : i;
  157.                     Variable v = board.get(row, col);
  158.                     tupleActualTotal += v.getValue();
  159.                     if (v.getValue() == 0) {
  160.                         amRow.variables().add(v);
  161.                         if (isRow) {
  162.                             v.setOrdinal(ordinal++);
  163.                         }
  164.                     }
  165.                    
  166.                 }
  167.                
  168.                 amRow.setRequiredSum(tupleTotals[i] - tupleActualTotal);
  169.                 Collections.sort(amRow.variables(), Variable.sortByOrdinal);
  170.                
  171.             }
  172.            
  173.         }
  174.        
  175.        
  176.         // Do both diagonals in a similar way -- isLead indicates which one
  177.         for (boolean isLead : new boolean[]{true, false}) {
  178.            
  179.             Tuple amRow = new Tuple();
  180.             augmentedMatrix.add(amRow);
  181.             int tupleActualTotal = 0;
  182.             int tupleTotal = isLead? leadingDiagTotal : minorDiagTotal;
  183.            
  184.             for (int i = 0; i < side; i++) {
  185.                
  186.                 int row = i;
  187.                 int col = isLead? row : side - row - 1;
  188.                 Variable v = board.get(row, col);
  189.                 tupleActualTotal += v.getValue();
  190.                 if (v.getValue() == 0) {
  191.                     amRow.variables().add(v);
  192.                 }
  193.                
  194.             }
  195.            
  196.             amRow.setRequiredSum(tupleTotal - tupleActualTotal);
  197.             Collections.sort(amRow.variables(), Variable.sortByOrdinal);
  198.            
  199.         }
  200.        
  201.         return augmentedMatrix;
  202.        
  203.     }
  204.  
  205.  
  206.  
  207.     /**
  208.      * A handy tot on how many tuple attempts has to be made to come up with solution.
  209.      */
  210.     int totalCombosDone = 0;
  211.    
  212.     /**
  213.      * "Constrain" a tuple of unknowns. That is, try out the possible values for
  214.      * the unknowns, given that some of them may already have been constrained
  215.      * by other tuples, and then recursively constrain more tuples until there
  216.      * are none left and the puzzle is solved.
  217.      *
  218.      * Whenever we try out a combination of values for this tuple, we "lock" the
  219.      * values we have tried, so that no other recursive attempt on a tuple can
  220.      * touch those values. Whenever we are exiting this recursive iteration, we
  221.      * unlock our values so that they are free to be altered again.
  222.      *
  223.      * @param combos
  224.      *            a convenience cache of combinations and sums, used to optimise
  225.      *            the selection of the next tuple to be constrained, based on
  226.      *            minimum combinations.
  227.      * @param augmentedMatrix
  228.      *            the full list of tuples
  229.      * @param n
  230.      *            the index into the augmented matrix of the tuple to be
  231.      *            constrained this time
  232.      * @return a boolean indicating if the puzzle is now solved
  233.      */
  234.     private boolean constrain(Combos combos, List<Tuple> augmentedMatrix, int n) {
  235.  
  236.         Tuple currTuple = augmentedMatrix.get(n); // current tuple
  237.        
  238.         // Get the remaining total that our unlocked unknowns must sum to
  239.         int remainingSum = currTuple.getRemainderSum();
  240.        
  241.         // Count the remaining unknown digits, and lock them
  242.         int remainingDigits = currTuple.lock();
  243.        
  244.         // Find out how many combinations of our remaining digits can sum to the
  245.         // required total
  246.         int numCombos = combos.getNumberCombos(remainingDigits, remainingSum);
  247.        
  248.         // Now try all those possible combinations:
  249.         int combosDone = 0;
  250.         DigitCombo d = new Base9(remainingDigits);
  251.         while (combosDone < numCombos) {
  252.             if (d.summedDigits() != remainingSum) {
  253.                 // keep counting until we get the necessary total
  254.                 d.increment();
  255.                 continue;
  256.             }
  257.             // if we get here then we have a match -- try it
  258.             combosDone++;
  259.             totalCombosDone++;
  260.             int i = 0;
  261.             System.out.printf("Try %s: ", getTupleName(n)); // convenience print
  262.                                                             // - comment out for
  263.                                                             // performance
  264.             // We have a set of digits to try. Set our "owned" variables with them
  265.             for (Variable v : currTuple.variables()) {
  266.                 if (v.getLock() == currTuple) { // i.e. owned by us
  267.                     int val = d.digits()[i++];
  268.                     v.setValue(val);
  269.                     // Convenience print to show value tried -- comment out for performance
  270.                     System.out.printf("B%s = %s; ", v.getOrdinal(), v.getValue()); //
  271.                 }
  272.             }
  273.             System.out.println(); // Comment out for performance
  274.            
  275.             // Now that we're trying a value that sums to the right number,
  276.             // select the next tuple to be constrained, based on minimum
  277.             // possible combinations.
  278.             int nextTuple = selectNextConstrainee(combos, augmentedMatrix);
  279.             if (nextTuple == -1) {
  280.                 // -1 indicates there are no tuples left to constrain, so we
  281.                 // are finished!
  282.                 currTuple.unlock();
  283.                 return true;
  284.             } else if (nextTuple >= -0) {
  285.                 // we found a tuple to constrain, so recursively constrain it.
  286.                 // If *it* returns true, we are finished.
  287.                 if (constrain(combos, augmentedMatrix, nextTuple)) {
  288.                     currTuple.unlock();
  289.                     return true;
  290.                 }
  291.             }
  292.             // If we get here, our combination was no use because we couldn't
  293.             // recursively constrain the remaining tuples. So try the next combination.
  294.             d.increment();
  295.         }
  296.        
  297.         // We have now tried all our combinations and if we get here it means that
  298.         // we couldn't satisfy our required total. We have to backtrack and let the
  299.         // previous tuple try another combination.
  300.         System.out.println("Stuck -- go back to previous"); // convenience print
  301.         currTuple.unlock();
  302.         return false;
  303.     }
  304.    
  305.     /**
  306.      * Select the next tuple from the augmented matrix which is not yet
  307.      * satisfied. Instead of just taking the next one in sequence, we poll them
  308.      * all to see how many possible combinations they might have to try, and we
  309.      * take the one with the minimum. This is intended as an optimisation
  310.      * (although it's not guaranteed to find the best route).
  311.      *
  312.      * @param combos
  313.      *            a convenience cache of numbers of combinations.
  314.      * @param augmentedMatrix
  315.      *            the augmented matrix
  316.      * @return the index into the augmented matrix of the next tuple to be
  317.      *         constrained. Negative values have special meaning: -1 means there
  318.      *         are no more tuples to constrain and the problem is solved; -2
  319.      *         means the problem is solved but there is no valid way forward so
  320.      *         we must backtrack.
  321.      */
  322.     int selectNextConstrainee(Combos combos, List<Tuple> augmentedMatrix) {
  323.        
  324.         // Track which tuple has minimum combos. Start with a crazy high number.
  325.         int minCombos = 1000000;
  326.        
  327.         // The selected tuple index, initially -1 which indicates no tuple
  328.         // is left unsatisfied.
  329.         int selectedTuple = -1;
  330.        
  331.         // An indicator value meaning "there is no possible solution from here"
  332.         final int kNoSolution = -2;
  333.        
  334.         // Test all tuples
  335.         for (int i = 0; i < augmentedMatrix.size(); i++) {
  336.            
  337.             Tuple tuple = augmentedMatrix.get(i);
  338.            
  339.             // Get the required remaining sum, discounting any variables already locked
  340.             int remainingSum = tuple.getRemainderSum();
  341.             int remainingDigits = tuple.getNumUnlocked();
  342.            
  343.             if (remainingDigits == 0) {
  344.                 // no digits left unlocked
  345.                 if (remainingSum == 0) {
  346.                     // this tuple is satisfied -- next
  347.                     continue;
  348.                 } else {
  349.                     // no digits left to set but wrong total -- no solution
  350.                     return kNoSolution;
  351.                 }
  352.             }
  353.            
  354.             if (remainingSum < 1) {
  355.                 // there are remaining digits but they must sum to less than 1 -- no solution
  356.                 return kNoSolution;
  357.             }
  358.            
  359.             // How many combinations of the remaining digits sum to required sum?
  360.             int numCombos = combos.getNumberCombos(remainingDigits, remainingSum);
  361.            
  362.             if (numCombos == 0) {
  363.                 // no combos = no solution
  364.                 return kNoSolution;
  365.             }
  366.            
  367.             // There are possible combos -- select the minimum number
  368.             if (numCombos < minCombos) {
  369.                 minCombos = numCombos;
  370.                 selectedTuple = i;
  371.             }
  372.         }
  373.        
  374.         return selectedTuple;
  375.     }
  376.  
  377.    
  378.     /**
  379.      * A convenience routine for getting a printable tuple name from
  380.      * its position in the augmented matrix.
  381.      *
  382.      * @param tupleNum position in matrix
  383.      * @return a convenience name for printing
  384.      */
  385.     String getTupleName(int tupleNum) {
  386.         int n = tupleNum;
  387.         // First 10 tuples are rows, next 10 are columns, then two diagonals
  388.         return n < 10 ? "row " + (n + 1) : n < 20 ? "column "
  389.                 + (n - 9) : "diagonal " + (n - 19);
  390.        
  391.     }
  392.    
  393. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement