Рекурсивный решатель судоку не работает в Java

Я написал игру судоку, включающую решатель на C, и хотел попробовать ее на Java, чтобы люди могли использовать ее немного проще (переносимость). Я полагал, что порт будет довольно простым из-за огромного сходства между языками, но, похоже, это немного затруднительно.

Мой решатель бесконечно рекурсирует, чего никогда не было в C. Вот моя исходная функция C для решения головоломки:

int sudoku_solve(struct sudoku* sudoku)
{
    if(!sudoku) return 0;

    int mask = 0x1ff;
    int best_x = 0, best_y = 0;
    int best_mask = 0x2ff;


    for(int y = 0; y < 9; ++y){
        for(int x = 0; x < 9; ++x){
            if( sudoku->grid[y][x] != 0 ) continue;
            mask = sudoku_get_mask(sudoku, x, y);
            if( mask < best_mask ){
                best_mask = mask;
                best_x = x;
                best_y = y;
            }
        }
    }

    if( best_mask == 0x2ff ) return 1; // this puzzle is already solved!

    if( best_mask == 0x000 ) return 0; // this puzzle can't be solved!

    int start_c = rand() % 9;
    int c = start_c;
    do{
        if( (best_mask & (1<<c)) ){
            sudoku->grid[best_y][best_x] = c+1;
            if( sudoku_solve(sudoku) ) return 1;
        }
        c = (c+1) % 9;
    } while( c != start_c );

    sudoku->grid[best_y][best_x] = 0;


    return 0;
}

Я знаю, что это не обязательно самый быстрый или лучший написанный решатель, но он работал. Он просто находит плитку с наименьшими возможными значениями, а затем начинает со случайных значений и пробует все возможные значения, пока не получится решаемая головоломка (с использованием рекурсии). sudoku_get_mask возвращает целое число с первыми 9 битами, установленными для соответствующего значения. Он проверяет горизонтальные, вертикальные и подквадраты на наличие значений, которые уже используются, и удаляет их из маски.

Теперь вот порт Java:

public int Solve()
{
    int mask = 0x2FF;
    int bmask = 0x2FF, bx = 0, by = 0;

    for(int y = 0; y < 9; ++y){
        for(int x = 0; x < 9; ++x){
            if( grid[y][x] != 0 ) continue; // ignore spaces with values already set
            mask = GetMask(x, y);
            if( mask < bmask ) // less bits set == less possible choices
            {
                bmask = mask;
                bx = x;
                by = y;
            }
        }
    }

    if( bmask == 0x2FF ) // the puzzle had no good slots, it must be solved
        return 1;

    if( bmask == 0 ) // the puzzle is unsolvable
        return -1;

    int start_c = rand() % 9;
    int c = start_c;
    do{
        if( (bmask & (1<<c)) != 0 ){
            grid[by][bx] = (char) (c+1);
            if( Solve() == 1 ) return 1;
        }
        c = (c+1)%9;
    }while( c != start_c );

    grid[by][bx] = 0; // restore old value

    return 0;
}

Они почти идентичны, поэтому я не могу понять, почему порт Java бесконечно повторяется! Решатель всегда должен либо 1. найти решение, либо 2. обнаружить, что решения нет. По моей логике я не вижу, как это должно повторяться бесконечно.

Вот Java-код GetMask:

protected int GetMask(int x, int y)
{
    int mask = 0x1FF;
    for(int cx = 0; cx < 9; ++cx){
        mask &= (grid[y][cx] == 0 ? mask : ~(1 << (grid[y][cx]-1)));
    }
    for(int cy = 0; cy < 9; ++cy){
        mask &= (grid[cy][x] == 0 ? mask : ~(1 << (grid[cy][x]-1)));
    }
    int idx = squareIndex[y][x];
    int[] pt = null;
    for(int c = 0; c < 9; ++c){
        pt = squarePoint[idx][c];
        mask &= (grid[pt[1]][pt[0]] == 0 ? mask : ~(1 << (grid[pt[1]][pt[0]]-1)));
    }
    return mask;
}

Вот SquareIndex и SquarePoint (просто поисковые таблицы для подквадратов):

static int squareIndex[][] = {
    {0,0,0,1,1,1,2,2,2},
    {0,0,0,1,1,1,2,2,2},
    {0,0,0,1,1,1,2,2,2},
    {3,3,3,4,4,4,5,5,5},
    {3,3,3,4,4,4,5,5,5},
    {3,3,3,4,4,4,5,5,5},
    {6,6,6,7,7,7,8,8,8},
    {6,6,6,7,7,7,8,8,8},
    {6,6,6,7,7,7,8,8,8}
};

static int[] squarePoint[][] = {
    { {0,0}, {1,0}, {2,0}, {0,1}, {1,1}, {2,1}, {0,2}, {1,2}, {2,2} },
    { {3,0}, {4,0}, {5,0}, {3,1}, {4,1}, {5,1}, {3,2}, {4,2}, {5,2} },
    { {6,0}, {7,0}, {8,0}, {6,1}, {7,1}, {8,1}, {6,2}, {7,2}, {8,2} },
    { {0,3}, {1,3}, {2,3}, {0,4}, {1,4}, {2,4}, {0,5}, {1,5}, {2,5} },
    { {3,3}, {4,3}, {5,3}, {3,4}, {4,4}, {5,4}, {3,5}, {4,5}, {5,5} },
    { {6,3}, {7,3}, {8,3}, {6,4}, {7,4}, {8,4}, {6,5}, {7,5}, {8,5} },
    { {0,6}, {1,6}, {2,6}, {0,7}, {1,7}, {2,7}, {0,8}, {1,8}, {2,8} },
    { {3,6}, {4,6}, {5,6}, {3,7}, {4,7}, {5,7}, {3,8}, {4,8}, {5,8} },
    { {6,6}, {7,6}, {8,6}, {6,7}, {7,7}, {8,7}, {6,8}, {7,8}, {8,8} }
};

person Caleb1994    schedule 07.02.2012    source источник
comment
Вы пытались выполнить код в отладчике?   -  person Grammin    schedule 07.02.2012
comment
В какой-то степени да. Я не смог найти проблему. Из-за случайных чисел и рекурсии очень сложно уследить, ха-ха.   -  person Caleb1994    schedule 07.02.2012
comment
что делает ваша функция GetMask?   -  person The Real Baumann    schedule 07.02.2012
comment
GetMask является точным эквивалентом sudoku_get_mask из C. Неважно, код не публикуется в комментариях. Позвольте мне отредактировать мой исходный пост с кодом GetMask.   -  person Caleb1994    schedule 07.02.2012
comment
Упс, забыл упомянуть, что rand() — это просто функция, которая возвращает генератор.nextInt(). Во время переноса было легче читать, если случайные числа выглядели одинаково.   -  person Caleb1994    schedule 07.02.2012
comment
А что такое generator?   -  person Mister Smith    schedule 07.02.2012
comment
Извините, это просто простой генератор java.util.Random.   -  person Caleb1994    schedule 07.02.2012
comment
Я запускал код какое-то время, кажется, он никогда не останавливается, но я не могу сказать вам, если что-то не так, потому что я не мог полностью понять алгоритм и у меня нет времени заняться им. В любом случае, совет: я думаю, что поиск с возвратом — лучший алгоритм для судоку.   -  person Mister Smith    schedule 07.02.2012
comment
Это тот же результат, который я получаю. Это алгоритм обратной трассировки. Он находит плитку с наименьшим количеством возможных значений и пробует каждое значение. Каждое пробуемое значение повторно запускает функцию решения. Если функция решения возвращает неразрешимую головоломку, она пробует следующее значение и так далее. Однако я заметил кое-что странное. Некоторые из моих случайных чисел получаются отрицательными! Скорее всего проблема в этом, но я не знаю почему. Я использую случайное целое число по модулю 9, которое должно давать число в диапазоне [0,9).   -  person Caleb1994    schedule 07.02.2012
comment
@ Caleb1994 Я тоже это заметил. Проблема в том, что в отличие от C, Random.nextInt возвращает значения в диапазоне [Integer.MIN_VALUE,Integer.MAX_VALUE], поэтому также создаются отрицательные случайные числа. Я предлагаю вам изменить его на Random.nextInt(9) и избавиться от %9.   -  person Mister Smith    schedule 07.02.2012
comment
@ Caleb1994 Давным-давно, когда я учился в университете, я запрограммировал решатель судоку. Помню, я использовал быструю эвристику, чтобы определить, будет ли ход правильным или нет: просто заметьте, что, поскольку числа от 1 до 9 могут встречаться только один раз в строке или столбце, сумма строки или столбца должна быть 45. (Впрочем, этого недостаточно для доказательства правильности хода, только для того, чтобы отбросить неправильные). Кроме того, для решения судоку сумма матрицы должна быть 45x9 = 405.   -  person Mister Smith    schedule 07.02.2012
comment
@MisterSmith О! Вот оно. Я не знал, что это нормально. Я гуглил о том, что randInt возвращает отрицательный результат, но я думаю, что все остальные в Интернете уже знали об этом, ха-ха. Спасибо.   -  person Caleb1994    schedule 07.02.2012


Ответы (1)


Я думаю, мистер Смит не собирается давать официальный ответ (я решил, что позволю ему за баллы).

проблема заключалась в том, что стандартная функция C rand() возвращает целые числа в диапазоне: [0,INT_MAX], а функция Java Randomizer.nextInt() находится в диапазоне [INT_MIN,INT_MAX]. Мне пришлось заменить «generator.nextInt() % 9» на «generator.randInt(9)», и это сработало.

person Caleb1994    schedule 12.02.2012