0

I'm making a Sudoku program, and I wanted to store every combination of x bits in an 81-bit integer into a list. I want to be able to then shuffle this list, iterate through it, and each on-bit will represent a cell that is to be removed from an existing Sudoku grid, x depending on difficulty. My program then tests this unique puzzle to see if it's solvable, if not, continue to the next combination. Do you guys understand? Is there a better way?

Currently I have a for-loop with a BigInteger, adding 1 every iteration, and testing to see if the resulting number has a number of bits on equal to 55. But this takes LOOOOOONG time. I don't think there's enough time in the universe to do it this way.

  LOOP: for(BigInteger big = new BigInteger("36028797018963967"); 
            big.compareTo(new BigInteger("2417851639229258349412351")) < 0;
            big = big.add(big.ONE))
            {
               int count = 0;
               for(int i = 0; i < 81; i++)
               {
                  if(big.testBit(i)) count++;
                  if(count > 55) continue LOOP;
               }
               //just printing first, no arraylist yet
               if(count == 55) System.out.println(big.toString(2));
            }
user3376587
  • 134
  • 2
  • 12
  • I'm not sure if this will improve the speed, but have you considered using Java's `BitSet` class? It has a `cardinality` method that returns the number of bits set. – David Choweller Mar 20 '17 at 03:34
  • You're dealing with a LOT of combos. To put it into perspective, a guy, using clever reasoning, used a program to prove that 16 was the minimum # starting numbers needed to solve the puzzle. It took him 9 months to run this program. – Nick Ziebert Mar 20 '17 at 03:45
  • 1
    That's going to be at least 3.6028797e+16 combinations right? It's going to take a LOOOOOONG time no matter what you do. – pvg Mar 20 '17 at 03:46
  • Actually, reading more carefully, he has only 1 completed Sudoku puzzle and wants to gray out x amount of locations. So it's 81 chose x? It's still a crap load. – Nick Ziebert Mar 20 '17 at 03:48
  • You seem to be right @pvg. I'll post an alternative answer in a moment. – user3376587 Mar 20 '17 at 03:51

3 Answers3

0

As you already noticed, storing all combinations in a list and then shuffling them is not a viable option. Instead, you can obtain a shuffled stream of all combinations, by using the Streamplify library.

import org.beryx.streamplify.combination.Combinations;
...
SudokuGrid grid = new SudokuGrid();
int[] solvedPuzzle = IntStream.range(0, 81).map(i -> grid.get(i)).toArray();
int k = 55;
new Combinations(81, k)
        .shuffle()
        .parallelStream()
        .map(removals -> {
            int[] puzzle = new int[81];
            System.arraycopy(solvedPuzzle, 0, puzzle, 0, 81);
            for(int i : removals) {
                puzzle[i] = 0;
            }
            return puzzle;
        })
        .filter(puzzle -> resolveGrid(new SudokuSolver(new Candidates(puzzle))))
        //.limit(10)
        .forEach(puzzle -> System.out.println(Arrays.toString(puzzle)));

You probably don't want to generate all puzzles of a given difficulty, but only a few of them. You can achieve this by putting a limit (see the commented line in the above code).

siordache
  • 112
  • 1
  • 3
-1

Certainly there are methods that will finish before you die of old age. For example:

Make an array (or BitSet, as David Choweller suggested in the comments) to represent the bits, and turn on as many as you need until you have enough. Then convert that back into a BigInteger.

D M
  • 1,410
  • 1
  • 8
  • 12
-1

I appreciate any feedback. The following seems to be a better option than my initial idea, since I believe having a list of all possible combinations would definitely give an out of memory error. It's not perfect, but this option takes out a random cell, tests to see if its solvable, if not put the last taken number back, and continue to remove the next random cell until enough cells have been taken out, or start over.

  int[] candidates = new int[81];
  SudokuGrid grid = new SudokuGrid();

  LOOP: while(true)
  {
     ArrayList<Integer> removals = new ArrayList<Integer>();
     for(int i = 0; i < 81; i++)
     {
        removals.add(i);
        candidates[i] = grid.get(i);
     }
     Collections.shuffle(removals);
     int k = 55;
     for(int i = 0; i < k; i++)
     {
        int num = candidates[removals.get(i)];
        candidates[removals.get(i)] = 0;
        cand = new Candidates(candidates);
        SudokuSolver solver = new SudokuSolver(cand);
        if(!resolveGrid(solver))
        {
           candidates[removals.get(i)] = num;
           k++;
           if(k > removals.size())
              continue LOOP;
        }
     }

     break;
  }

This takes about 5 seconds to solve. It's a bit slower than I wanted it to be, but a lot of it depends on the way I coded the solving strategies.

user3376587
  • 134
  • 2
  • 12