Решатель судоку Erlang - Как найти пустые места и рекурсивно попробовать возможные значения

Вчера и сегодня я был занят решателем судоку в Erlang. Рабочая функциональность, которая у меня есть сейчас, заключается в том, что я могу проверить, есть ли судоку в виде списка, например,

[6,7,1,8,2,3,4,9,5,5,4,9,1,7,6,3,2,8,3,2,8,5,4,9,1,6,7,1,3,2,6,5,7,8,4,9,9,8,6,4,1,2,5,7,3,4,5,7,3,9,8,6,1,2,8,9,3,2,6,4,7,5,1,7,1,4,9,3,5,2,8,6,2,6,5,7,8,1,9,3,4].

действителен или нет, глядя на ограничения (нет дубликатов в квадратах, строках и столбцах).

Эта функция называется valid(S), которая принимает судоку S и возвращает true, если это действительное судоку, и false, если это не так. Функция игнорирует 0, которые используются для представления пустых значений. Это пример той же судоку с некоторыми случайными пустыми значениями:

[0,7,1,8,2,3,4,0,5,5,4,9,0,7,6,3,2,8,3,0,8,5,0,9,1,6,7,1,3,2,6,5,7,8,4,9,0,8,6,4,1,2,5,7,0,4,5,7,3,9,8,6,1,0,8,9,3,2,6,4,7,5,1,7,1,4,9,3,0,2,8,6,2,6,5,7,8,1,9,3,4].

Следующий шаг — найти первый 0 в списке, попробовать значение от 1 до 9 и проверить, выдает ли он допустимую судоку. Если это так, мы можем перейти к следующему 0 и попробовать значения там и посмотреть, действительно ли оно или нет. Как только мы не можем идти дальше, мы возвращаемся к предыдущему 0 и пробуем следующие значения и так далее, пока не получим решенную судоку.

Код, который у меня есть до сих пор, выглядит так (на основе кого-то, у кого он почти работает):

solve(First,Nom,[_|Last]) -> try_values({First,Nom,Last},pos()).

try_values(_,[]) -> {error, "No solution found"};
try_values({First,Nom,Last},[N|Pos]) ->
  case valid(First++[N]++Last) of
    true ->
      case solve({First++[N]},Nom,Last) of
        {ok,_} -> {ok, "Result"};
        {error,_} -> try_values({First,N,Last},Pos)
      end;
    false -> try_values({First,N,Last},Pos)
  end.

pos() — это список, состоящий из значений от 1 до 9. Идея состоит в том, что мы вводим пустой список для First и список судоку для [_|Last], в котором мы ищем 0 (Nom?). Затем мы пробуем значение, и если список, который получается, действителен в соответствии с нашей функцией, мы продолжаем, пока не провалим позицию или не получим результат. В случае неудачи мы возвращаем новое значение try_values ​​с оставшимися (Pos) значениями наших возможностей.

Естественно, это не работает и возвращает:

5> sudoku:solve([],0,S).
** exception error: bad argument
     in operator  ++/2
        called as {[6]}
                  ++
                  [1,1,8,2,3,4,0,5,5,4,9,0,7,6,3,2,8,3,2,8,5,4,9,1,6,7,1,3,2|...]
     in call from sudoku:try_values/2 (sudoku.erl, line 140)
     in call from sudoku:try_values/2 (sudoku.erl, line 142)

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


person Erwin Rooijakkers    schedule 04.10.2013    source источник
comment
Ошибка исходит от solve({First++[N]},Nom,Last), насколько я могу судить, solve ожидает, что First будет списком, тогда вы, вероятно, имели в виду solve(First++[N],Nom,Last)   -  person zaquest    schedule 04.10.2013
comment
Привет вам еще раз, спасибо! :-) Это было действительно неправильно. К сожалению, у меня все еще есть проблема. Основная проблема, с которой я сталкиваюсь, заключается в том, что я не понимаю, как рекурсивно перебирать 0. Я думаю, что переменная Nom должна работать с этим, но я не знаю, как это сделать. Полный код находится здесь github.com/erooijak/Erlang/blob/master/sudoku. Эрл.   -  person Erwin Rooijakkers    schedule 05.10.2013


Ответы (1)


try_values([], []) -> error("No solution found");
try_values([Solution], []) -> Solution;
try_values(_, []) -> error("Bad sudoku: multiple solutions");
try_values(Heads, [0|Tail]) ->
    NewHeads = case Heads of
                   [] -> [[P] || P <- pos()];
                   _ -> [Head++[P] || P <- pos(), Head <- Heads]
              end,
    ValidHeads = [Head || Head <- NewHeads, valid(Head++Tail)],
    try_values(ValidHeads, Tail);
try_values([], [H|Tail]) -> try_values([[H]], Tail);
try_values(Heads, [H|Tail]) -> try_values([Head++[H] || Head <- Heads], Tail).

solve(Board) ->
    case valid(Board) of
        true -> try_values([], Board);
        false -> error("No solution found")
    end.

try_values делает то, что вы описали. Он строит решение, проходя Board, пробуя все возможные решения (из pos()), когда находит 0, и собирая valid решений в ValidHeads, чтобы передать их дальше, чтобы продолжить. Таким образом, это идет всеми возможными путями, если в какой-то момент будет несколько valid судоку, все они будут добавлены в Heads и будут проверены на validity на следующих шагах. solve — это просто оболочка для вызова try_values([], Board).

По сути, путь к iterate recursively over 0's состоит в том, чтобы пропустить все ненулевые значения (2 последних выражения try_values) и выполнить работу с нулями (четвертое выражение try_values).

Первые три выражения try_values проверяют, существует ли решение и единственное ли оно, и возвращают его в этом случае.

person zaquest    schedule 05.10.2013
comment
Большое спасибо! У меня проблемы с пониманием части dropwhile. Голова ( fun (H) ? ) удаляется из списка, когда head + tailH++tail недействительны. А потом вы используете конец и ставите NewHeads после запятой? Руководство по Erlang для dropwhile/2 не объясняет, что это делает. Я также не понимаю, как из этого может следовать случай [] и [Validhead|_]. И последнее, но не менее важное: для несколько более сложных судоку говорится, что решение не найдено (хотя решение есть). Я не понимаю, как это может быть, потому что это просто грубая сила. - person Erwin Rooijakkers; 05.10.2013
comment
@user2609980 user2609980 извините, мой плохой. Я сделал это неправильно. Я предположил, что в любой момент есть только одно valid решение, и пропустил все остальные. Таким образом, предыдущий код работал только в одном направлении в дереве всех возможных досок. Я обновил свой ответ. dropwhile/2 пропускает элементы списка до тех пор, пока заданный предикат не станет true, а затем возвращает остальную часть списка. Таким образом, когда dropwhile соответствовало [], не было допустимых решений, а когда оно соответствовало [ValidHead|_], допустимым было одно из возможных решений. В любом случае это было неправильно. - person zaquest; 05.10.2013
comment
Ага! Теперь я понимаю, что пошло не так с предыдущим кодом. И последний бит, который я не понял, теперь понятен после того, как вы поставили ввод в коде. Естественно, если у судоку есть несколько решений, это неплохо, но оно должно отображать два решения. Я оставляю это как упражнение себе ;-). Большое спасибо, сэр! - person Erwin Rooijakkers; 05.10.2013
comment
Он работает с большинством судоку, но когда я пытаюсь решить самую сложную судоку в мире, он думает 10 минут, а затем выдает следующую ошибку: Аварийный дамп был записан в: erl_crash.dump eheap_alloc: невозможно выделить 1366780092 байта памяти (типа кучи) . Как это может быть? :-) - person Erwin Rooijakkers; 08.10.2013
comment
@user2609980 user2609980 Это означает, что алгоритм (брутфорс) недостаточно хорош и требует слишком много памяти для этих случаев. Вам нужно найти лучший алгоритм решения судоку. - person zaquest; 08.10.2013