Я пишу алгоритм на PHP для решения заданной головоломки судоку. Я создал несколько объектно-ориентированную реализацию с двумя классами: класс Square для каждой отдельной плитки на плате 9x9 и класс Sudoku, который имеет матрицу Square для представления платы.
Реализация используемого мной алгоритма представляет собой своего рода трехуровневый подход. Первый шаг, который решит только самые простые головоломки (но наиболее эффективен), - заполнить любые квадраты, которые могут принимать только одно значение в зависимости от начальной настройки доски, и соответствующим образом скорректировать ограничения для остальных элементов. неразрешенные квадраты.
Обычно этот процесс «постоянного распространения» не решает доску полностью, но он решает значительный кусок. Затем сработает второй уровень. Он анализирует каждую единицу (или 9 квадратов, которые все должны иметь уникальные номера, например, строку или столбец) на предмет «возможных» значений каждого нерешенного квадрата. Этот список возможных значений представлен в виде строки в классе Square:
class Square {
private $name; // 00, 01, 02, ... , 86, 87, 88
private $peers; // All squares in same row, col, and box
private $number; // Assigned value (0 if not assigned)
private $possibles; // String of possible numbers (1-9)
public function __construct($name, $p = 0) {
$this->name = $name;
$this->setNumber($p);
if ($p == 0) {
$this->possibles = "123456789";
}
}
// ... other functions
Учитывая целый массив нерешенных квадратов в единице (как описано выше на втором уровне), второй уровень объединит все строки «возможных» в одну строку. Затем он будет искать в этой единственной строке любые уникальные символьные значения - значения, которые не повторяются. Это будет означать, что в пределах единицы квадратов есть только один квадрат, который может принимать это конкретное значение.
Мой вопрос: для реализации этого второго уровня, как я могу проанализировать эту строку всех возможных значений в модуле и легко определить уникальное значение (я)? Я знаю, что могу создать массив, в котором каждый индекс представлен числами 1-9, и я могу увеличить значение соответствующего индекса на 1 для каждого возможного значения этого числа, которое я нахожу, а затем снова сканировать массив для любых значения 1, но это кажется крайне неэффективным, требуя двух линейных сканирований массива для каждой единицы, а в головоломке судоку 27 единиц.






Это несколько похоже на то, что вы уже исключили как «крайне неэффективное», но со встроенными функциями, поэтому оно может быть весьма эффективным:
$all_possibilities = "1234567891234";
$unique = array();
foreach (count_chars($all_possibilities, 1) as $c => $occurrences) {
if ($occurrences == 1)
$unique[] = chr($c);
}
print join("", $unique) . "\n";
Печать: «56789»
function singletonsInString($instring) {
$results = array();
for($i = 1; $i < 10; $i++) {
$first_pos = strpos($instring, str($i));
$last_pos = strrpos($instring, str($i));
if ( $first_pos !== FALSE and $first_pos == $last_pos )
$results[] = $i;
}
return $results;
}
Это даст вам все до единого. Получите первую и последнюю позиции числа в этом массиве, и если они совпадают и не являются одновременно ЛОЖНЫМИ (строгое сравнение в случае, если в начале есть одноэлемент), то в этом массиве есть только одно такое число.
Если вы очень беспокоитесь о скорости здесь, вы, вероятно, можете заменить внутреннюю часть этой петли на
$istr = str($i);
if ( ($first = strpos($instring, $istr)) !== FALSE
and $first == $strrpos($instring, $istr) ) $results[] = $i;
за минимальное количество вычислений. Что ж, если предположить, что собственный strpos PHP - лучший способ решить эти проблемы, что, насколько я знаю, небезосновательно.
Вместо этого подумайте об использовании двоичного числа для представления ваших «возможных», потому что двоичные операции, такие как AND, OR, XOR, как правило, намного быстрее, чем операции со строками.
Например. если для квадрата возможны «2» и «3», используйте двоичное число 000000110, чтобы представить возможности для этого квадрата.
Вот как можно найти уникальных посетителей:
$seenonce = 0;
$seenmore = 0;
foreach(all_possibles_for_this_unit as $possibles) {
$seenmore |= ($possibles & $seenonce);
$seenonce |= $possibles;
}
$seenonce ^= $seenmore;
if ($seenonce) {
//something was seen once - now it must be located
}
Я не уверен, действительно ли этот метод будет работать быстрее, но на него стоит обратить внимание.
В последний раз, когда я решал судоку, у меня был третий урок под названием «Беги». Экземпляр Run создается для каждой строки, столбца и квадрата 3x3. С каждым квадратом связано три пробега. Класс Run содержит набор чисел, еще не помещенных в цикл. Затем решение доски включает в себя итеративное пересечение множеств в каждом квадрате. Это касается 80% большинства досок среднего размера и 60% большинства жестких досок. После того, как вы прошли всю доску без изменений, вы можете перейти к логике более высокого уровня. Каждый раз, когда ваша логика более высокого уровня заполняет квадрат, вы снова проходите через свои квадраты.
Хорошая вещь в этой настройке - вы можете легко добавлять варианты в решатель. Допустим, вы используете вариант, в котором две диагонали также уникальны. Вы просто добавляете 4-й проход к этим 18 квадратам.
Что бы я сделал, это на самом деле использовать двоичные биты для хранения фактических значений, как предложил другой ответ. Это позволяет выполнять эффективные проверки и в целом может предоставить судоку более математическое (= эффективное и более короткое) решение (просто мое впечатление, я не исследовал это).
По сути, вы представляете числа в квадратах не цифрами, а степенями двойки.
"1" = 2^0 = 1 = 000000001
"2" = 2^1 = 2 = 000000010
"3" = 2^2 = 4 = 000000100
"4" = 2^3 = 8 = 000001000
... etc up to
"9" = 2^8 = 256= 100000000
Таким образом, вы можете просто добавить содержимое отдельных квадратов, чтобы узнать, какие числа отсутствуют в 3x3, строке или любом другом подмножестве судоку, например:
// shows the possibles for 3x3 square number 1 (00-22)
$sum=0;
for ($i=0; $i< 3; $i++)
for ($j=0; $j < 3; $j++)
$sum += $square["${i}${j}"]->number
$possibles = $sum ^ 511 // ^ stands for bitwise XOR and 511 is binary 11111111
теперь $ possibles содержит "1" в битовых позициях цифр, которые возможны в этом квадрате, и вы можете выполнять побитовые операции с результатами для других квадратов, чтобы сопоставить их вместе, например:
например. скажем:
$possibles1 = 146 // is binary 100100101,
//indicating that this row or 3x3 square has place for "9", "6", "3" and "1"
$possibles2 = 7 // is binary 000000111, indicating it has place for "3", "2" and "1".
// so:
$possibles1 & $possibles2
// bitwise AND, will show binary 101 saying that "3" and "1" is unfilled in both bloces
$possibles1 | $possibles2
// bitwise OR will give that in total it is possible to use "9", "6", "3", "2" and "1" in those two squares together
Вот способ использования только встроенных функций PHP, который должен быть довольно быстрым.
function getUniques($sNumbers)
{
return join(array_keys(array_count_values(str_split($sNumbers)),1));
}
echo getUniques("1234567891234"); // return 56789;