Pull to refresh

Решаем судоку на JavaScript

Reading time4 min
Views35K
СудокуСудоку — популярная головоломка, основной задачей которой является размещение цифр в правильном порядке.

Игровое поле представляет собой квадрат 9х9 клеток. Клетки сгруппированы в девять сегментов 3х3. В каждой клетке может быть записана цифра от одного до девяти. Основным правилом судоку является то, что цифра не может повторяться в строке, столбце и сегменте.

Под катом приводится алгоритм решения судоку, реализованный на JavaScript. Рассмотрены только базовые тактики решения головоломки, но этого достаточно для большого числа судоку легкого и среднего уровня.

Основная идея


Ячейка на поле может иметь одно из трех состояний:
  • in — значение определено изначально
  • unknown — значение не определено
  • solved — значение найдено
Для каждой нерешенной ячейки хранится массив предполагаемых значений, которые она может принять (кандидатов). Чтобы решить судоку достаточно поочередно перебирать нерешенные ячейки и уменьшать количество кандидатов в каждой из них. Если количество кандидатов сокращается до одного, то значение ячейки найдено и ей присваивается статус solved.

Поиски решения заканчиваются, если после перебора всех нерешенных ячеек ни для одной из них не удалось найти решение или не было изменено количество кандидатов:
/**
* Решение судоку
*
* Метод в цикле пытается решить судоку, если на текущем этапе не изменилось
* ни одного элемента, то решение прекращается.
*/
function solve() {
  var changed = 0;
  do {
    // сужаем множество значений для всех нерешенных чисел
    changed = updateSuggests();
    steps++;
    if ( 81 < steps ) {
      // Зашита от цикла
      break;
    }
  } while (changed);
}; // end of method solve()
 
/**
* Обновляем множество предположений
*
* Проверяем основные правила -- уникальность в строке, столбце и секции.
*/
function updateSuggests() {
  var changed = 0;
  var buf = arrayDiff(solved[1][3][2], rowContent(1));
  buf = arrayDiff(buf, colContent(3));
  buf = arrayDiff(buf, sectContent(1, 3));
  for ( var i=0; i<9; i++) {
    for ( var j=0; j<9; j++) {
      if ( 'unknown' != solved[i][j][1] ) {
        // Здесь решение либо найдено, либо задано
        continue;
      }
      // "Одиночка"
      changed += solveSingle(i, j);
      // "Скрытый одиночка"
      changed += solveHiddenSingle(i, j);
    }
  }
  return changed;
}; // end of method updateSuggests()

Реализация алгоритма


Есть много способов решения судоку, но в целях демонстрации рассмотрим два наиболее простых метода: «Одиночка» и «Скрытый одиночка». Это базовые методы решения судоку и они отлично подходят для изучения основных принципов.

Метод «Одиночка» основывается непосредственно на правилах судоку: любая цифра может встречаться только один раз в строке колонке и сегменте. С точки зрения алгоритма это означает, что из списка кандидатов ячейки будут вычеркиваться все найденные числа из ее строки, столбца и сегмента.
/**
* Метод "Одиночка"
*/
function solveSingle(i, j) {
  solved[i][j][2] = arrayDiff(solved[i][j][2], rowContent(i));
  solved[i][j][2] = arrayDiff(solved[i][j][2], colContent(j));
  solved[i][j][2] = arrayDiff(solved[i][j][2], sectContent(i, j));
  if ( 1 == solved[i][j][2].length ) {
    // Исключили все варианты кроме одного
    markSolved(i, j, solved[i][j][2][0]);
    return 1;
  }
  return 0;
}; // end of method solveSingle()

Метод «Скрытый одиночка» использует правило: если среди кандидатов в строке/столбце/сегменте отдельная цифра встречается только в одной ячейке, то значение этой ячейки и будет равняться такой цифре.
/**
* Метод "Скрытый одиночка"
*/
function solveHiddenSingle(i, j) {
  var less_suggest = lessRowSuggest(i, j);
  var changed = 0;
  if ( 1 == less_suggest.length ) {
    markSolved(i, j, less_suggest[0]);
    changed++;
  }
  var less_suggest = lessColSuggest(i, j);
  if ( 1 == less_suggest.length ) {
    markSolved(i, j, less_suggest[0]);
    changed++;
  }
  var less_suggest = lessSectSuggest(i, j);
  if ( 1 == less_suggest.length ) {
    markSolved(i, j, less_suggest[0]);
    changed++;
  }
  return changed;
}; // end of method solveHiddenSingle()
 
/**
* Минимизированное множество предположений по строке
*/
function lessRowSuggest(i, j) {
  var less_suggest = solved[i][j][2];
  for ( var k=0; k<9; k++ ) {
    if ( k == j || 'unknown' != solved[i][k][1] ) {
      continue;
    }
    less_suggest = arrayDiff(less_suggest, solved[i][k][2]);
  }
  return less_suggest;
}; // end of method lessRowSuggest()
 
/**
* Минимизированное множество предположений по столбцу
*/
function lessColSuggest(i, j) {
  var less_suggest = solved[i][j][2];
  for ( var k=0; k<9; k++ ) {
    if ( k == i || 'unknown' != solved[k][j][1] ) {
      continue;
    }
    less_suggest = arrayDiff(less_suggest, solved[k][j][2]);
  }
  return less_suggest;
}; // end of method lessColSuggest()
 
/**
* Минимизированное множество предположений по секции
*/
function lessSectSuggest(i, j) {
  var less_suggest = solved[i][j][2];
  var offset = sectOffset(i, j);
  for ( var k=0; k<3; k++ ) {
    for ( var l=0; l<3; l++ ) {
      if ( ((offset.i+k) == i && (offset.j+l) == j)|| 'unknown' != solved[offset.i+k][offset.j+l][1] ) {
        continue;
      }
      less_suggest = arrayDiff(less_suggest, solved[offset.i+k][offset.j+l][2]);
    }
  }
  return less_suggest;
}; // end of method lessSectSuggest()

Использование


Весь код оформлен в виде класса Sudoku. Для решения головоломки необходимо создать экземпляр класса, передать в него условие задачи и вызвать метод solve(). Результат решения выводится методом html(), который возвращает отформатированный таблицу и количество проходов, которое потребовалось для поиска решения. Если решение не может быть найдено, то будет выведена таблица найденных результатов и по наведению курсора на пустые ячейки будет показываться список кандидатов.

Исходные значения задаются целочисленной матрицей 9х9. В ячейках записывается «0», если значение не определено и число иначе.
var in_val = [
  [0, 0, 3, 0, 0, 8, 2, 0, 4],
  [0, 2, 0, 0, 6, 4, 0, 1, 0],
  [9, 0, 0, 0, 0, 0, 0, 0, 8],
  [0, 8, 0, 0, 0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0, 6, 9, 8, 0],
  [0, 0, 0, 0, 0, 0, 5, 0, 0],
  [0, 0, 4, 9, 0, 7, 0, 3, 0],
  [8, 0, 0, 0, 0, 1, 0, 0, 0],
  [0, 7, 0, 0, 5, 0, 4, 0, 0]
];
 
var sudoku = new Sudoku(in_val);
document.write(sudoku.html());

Результат работы алгоритма выглядит так:

Результат работы алгоритма

Исходный код класса Sudoku, а также несколько примеров задач доступны по этой ссылке.

Ссылки


Судоку [Википедия]
Как решать судоку: способы, методы и стратегия

UPD Пользователь rozboris любезно создал демку

UPD 2 Реализован обратный поиск. Теперь решает все судоку. И новая демка от rozboris.

UPD3 Так как ссылки в статье умерли, выложил исходники на Google Code.
Tags:
Hubs:
Total votes 55: ↑49 and ↓6+43
Comments28

Articles