Backtracking algorithm to generate a sudoku grid does not complete - php

I wrote a sudoku generator that creates numbers cell by cell and checks immediately after a cell has been created if it is valid (horizontally, vertically and in a 3x3 block).
Now my problem is that the algorithm always gets stuck at some point, as it won't find a valid number for the current cell. Sometimes closer to the end, sometimes already after writing 30 cells.
This is my function to check the cell, which should change the number depending on its validity:
private function checkCell($index)
{
while ($this->isValid($index) === false) {
$this->cell[$index]->setValue(rand(1, 9));
$this->counter++;
echo 'counter: ' . $this->counter;
echo PHP_EOL;
if ($this->counter > 1000) {
$this->display();
die();
}
}
}
isValid() checks if the cell is valid horizontally, vertically and in a block (this is currently not working, it just returns true).
The counter is for debugging purposes so I can see when it gets stuck.
Here is the function generating my cells:
private function fillCell($index)
{
$rand = rand(1, 9);
$this->cell[$index]->setValue($rand);
$this->checkCell($index);
}
What should be changed so the algorithm doesn't get stuck all the time?

The issue might be that the algorithm is a little too random. You end up creating a grid that is invalid and can not be completed further.
I would propose starting from a known valid grid and shuffle the cells randomly. If a cell can't be moved, we can simply skip it.
A fair warning to the reader, the following will contain pseudo code rather than working code.
A perfectly valid starting grid:
1 2 3 | 4 5 6 | 7 8 9
7 8 9 | 1 2 3 | 4 5 6
4 5 6 | 7 8 9 | 1 2 3
------|-------|------
9 1 2 | 3 4 5 | 6 7 8
6 7 8 | 9 1 2 | 3 4 5
3 4 5 | 6 7 8 | 9 1 2
------|-------|------
8 9 1 | 2 3 4 | 5 6 7
5 6 7 | 8 9 1 | 2 3 4
2 3 4 | 5 6 7 | 8 9 1
We can store this in a single dimension array, as you already appear to do.
We follow a simple logic:
We create a single dimension array containing the cells
$cells = array(
1,2,3,4,5,6,7,8,9,7,8,9,1,2,3,4,5,6,4,5,6,7,8,9,1,2,3,
9,1,2,3,4,5,6,7,8,6,7,8,9,1,2,3,4,5,3,4,5,6,7,8,9,1,2,
8,9,1,2,3,4,5,6,7,5,6,7,8,9,1,2,3,4,2,3,4,5,6,7,8,9,1,
);
We create another array containing numbers from 0 to 80 in a random order, which are the indexes of $cells
$indexes = range(0, 80);
shuffle($indexes);
We iterate over $indexes and use the value to select a random $cell in $cells
foreach($indexes as $index) {
$cell = $cells[$index];
}
For each $cell, we iterate over $cells. In each iteration, we create a temporary grid where we switch the value of the current cell with the value of the target cell. If the temporary grid is valid, we save the target index in an array of candidates
// pseudo code because that's a lot of work
$candidates = getCandidates($cell, $cells);
We randomly choose one of the candidates and switch the cells. If no candidate is available, we simply ignore this step
candidatesCount = count(candidates);
if(candidatesCount > 0) {
$candidate = $candidates[range(0, candidatesCount -1)];
// switch values
$cells[$index] = $cells[$candidate];
$cells[candidate] = $cell;
}
Repeat until $cells is processed
There are likely more efficient ways to proceed, but this logic can not get stuck.
Note that there is a low probability that the shuffle will undo itself and produce the original grid. But it's still a valid grid.

You never want to make a backtracking algorithm that uses random numbers. It can end up running infinitely.
What you want to do is:
Find the first empty cell
Try all possible values from 1 to 9 in this cell. When all values are tried, go back.
For every value you try in the cell (at step 2), recursively call the backtracking algorithm. (go back to step 1)
If the function is called and there are no empty cells, evaluate the board. If everything is ok, you found the solution! If it's not ok, go back.
The evaluation means, check that you have all numbers from 1 to 9 exactly once on every line, every column, and every 3x3 square.
Example of how it might look like:
function back($pos) {
if ($pos >= 9*9) {
if (evaluate()) {
// we found a solution
// do soemthing with it
} else {
return;
}
}
$x = pos / 9;
$y = pos % 9;
if ($m[x][y] != 0) {
// we already have a value assigned for this position
back($pos+1);
return;
}
for ($v = 1; $v <= 9; $v++) {
$m[x][y] = $v;
back($pos+1);
}
$m[x][y] = 0; // clean up tested value before going back
}
back(1)
The above algorithm can be optimized by evaluating lines/columns at every step, instead of just once at the end. If the algorithm tries to place number x, but x is already found on the line/column, then we can just move on to try x+1 since we know x will create an invalid solution.

Related

How Do I Implement Incomplete Answer to Old Question

This comment looks like it would work if the author included the value for $numbers. They say it is some type of array, but don't provide enough information to replicate it. I picture some hard coded array ranging from 0 to 9, but I can't help think that such an array would miss numbers greater than 9. What does the numbers array in this example look like?
$text = "1 out of 23";
if(preg_match_all('/\d+/', $text, $numbers))
$lastnum = end($numbers[0]);
I would just post a comment asking whoever wrote that to paste the value for $numbers, but it says I need reputation points to do that.
See How do I grab last number in a string in PHP?
To answer your initial question print_r() can be used to output all contents of an array. e.g. print_r($numbers)
https://3v4l.org/2jA1b
To explain the code:
\d is a single number
+ is a quantifier meaning one or more of the previous character or group
so this would find all numbers in a string. The $numbers[0] would be all numbers, 1 per index, and the end() pulls to the last number/index. Each index would be a number, the 0 is all matches, each indice at the root level is a capture group.
This code wouldn't work as intended for decimals or comma delimited integers. In those cases the numbers would be split up at the delimiter. 1.0 would become 1 and 0 (2 different numbers).
You could rewrite this as:
$text = "1 out of 23";
if(preg_match('/.*\K\D\d+/', $text, $numbers))
echo $numbers[0];
so the end function is not needed. This pulls everything until the last number then forgets everything before the last number.
What you are trying to do is likely easier using preg_split instead of preg_match_all. We can split the input text by the matched regex (digits) and then rebuild the string while incrementing the numbers as we go.
<?php
function incrementNumbers($text) {
// NOTES:
// parenthesis are important in the regex in order to return the captured values
// the -? will capture negative numbers too if necessary
// PREG_SPLIT_DELIM_CAPTURE allows the captured values to be returned too
$split = preg_split('/(-?\d+)/', $text, -1, PREG_SPLIT_DELIM_CAPTURE);
$return = '';
foreach($split as $i => $s) {
// because we didn't use PREG_SPLIT_NO_EMPTY, $split[0] will either be an empty string if
// $text began with a number, or the text before the first number. Either way, $split alternates
// between non-number [0], number [1], non-number [2], number [3], etc which is why we can detect
// even or odd indexes to determine if this is a number that needs to be incremented or not
if ($i % 2 === 0) {
$return .= $s;
} else {
$return .= (intval($s) + 1);
}
}
return $return;
}
Examples:
echo incrementNumbers("1 out of 23 with 1 and 1 and 24 and 23");
echo incrementNumbers("1 1 2 2 3 3 2 2 1 1");
echo incrementNumbers("0 1 2 3 4 5 6 7");
echo incrementNumbers("-3 -2 -1 0 1 2 3 4 5 6 7");
echo incrementNumbers("there are no numbers in this text");
echo incrementNumbers("does not start 999 with a number 123 nor end 17 with a number");
Outputs:
2 out of 24 with 2 and 2 and 25 and 24
2 2 3 3 4 4 3 3 2 2
1 2 3 4 5 6 7 8
-2 -1 0 1 2 3 4 5 6 7 8
there are no numbers in this text
does not start 1000 with a number 124 nor end 18 with a number
Working example at https://3v4l.org/iKskO

I want to print numbers pattern program but it not give me wrong out put

I want to get my output like this
1 5 9 13
2 6 10 14
3 7 11 15
4 8 12 16
I am trying like this:
<?php
for($a=1; $a<=16; $a++)
{
for($b=$a; $b>=1; $b--)
{
echo "$b";
}
echo "<br>";
}
?>
The above code gives me the wrong output.
Let's debug.
You are starting from 1 in your outer loop and in your inner loop, you are going from $a till 1 times.
This doesn't comply with your requirements because we have to print an increasing sequence in each row.
You can also notice that every number in a row differs by 4.
So, logic would be like below:
Pseudocode:
rows = 4
starting_number = 1
loop from 1 to rows
number = starting_number
loop from 1 to 4 // since each row has 4 numbers
print number
number += 4
print new_line
starting_number++
Demo: https://3v4l.org/9YjIP

Bitwise Compressed Booleans - Toggling Specific Ones

Background
I have to store a number of true/false values in a database, and rather than make many columns, I instead am using a single int column, where I store all of the booleans, compressed using Bitwise operators.
Example:
Product 1 is certified as:
Lead Free - Yes
Gluton Free - No
Free Range - Yes
Organic - Yes
So in the DB, in the certs column, I'd compress 1, 0, 1, 1 into 13 (1 + 4 + 8).
I've written quick wrapper functions to compress and extract this information (change from int into boolean array and back).
Problem
I'm not sure of the best way to quickly add and remove values from this. Say I want to update the product to NOT be Free Range anymore. I can take the compressed int, minus 4. That works. Except what if it already wasn't Free Range? If I'm doing bulk operations, I need my function to only remove 4 from products that are currently certified Free Range, or the number gets messed up.
I have figured it out with a lengthy if statement, but it's not elegant. This works:
// $certs_current is the compressed int from the DB.
// $certs_new is a new integer. 4 would mean toggle "Free Range" to true. -4 would mean toggle it to false.
if ( $certs_current & abs($certs_new) ) {
if ( $certs_new < 0 ) {
$certs_current += $certs_new;
}
} else {
if ( $certs_new > 0 ) {
$certs_current += $certs_new;
}
}
This is a lot of if statements. I played around with the | operator, but it only works for adding positive numbers. Pass it a -4 and it breaks. And I can't figure out a nor operator.
Is there a better way to do this?
Edit:
In other words, is there an operator where: If I give it 13 and -4, it'll give me 9. But if I give it 9 and -4, it'll give 9? Or even just 13 and 4.
Edit 2
Adding in the | to my working logic, I've reduced the complexity and still have it working:
if ( $certs_new > 0 ) {
$certs_current = ( $certs_current | $certs_new );
} else {
if ( $certs_current & abs($certs_new) ) {
$certs_current += $certs_new;
}
}
Basically it says, if the new number is positive, then use the | operator to add it if it needs to be added. If it's negative, then we first check to see if we need to remove it, and if so, remove it. Which is the place where I think we can improve.
The | replaces an if statement, because it only adds the 4 if the 4 isn't already toggled to true (I realized that's a weird way to describe it).
But with the negative number, I still have a nested if statement, which is what I want to remove if possible.
You have the compressed value, so why don't you expand it before doing your operations?
$certs_temp = $certs_current
$is_organic = $certs_temp >= 8 && $certs_temp -= 8
$is_freerange = $certs_temp >= 4 && $certs_temp -= 4
$is_glutenfree = $certs_temp >= 2 && $certs_temp -= 2
$is_leadfree = $certs_temp >= 1 && $certs_temp -= 1
And then re-compress with the changed values (if indeed anything has changed).
I think you are asking for trouble if you try to operate directly on the stored integer, which actually represents 4 separate binary flags. Either represent each flag separately in the DB, or if you must have it stored as an integer, then decode it before applying changes or logic.
Answer
I figured it out. To toggle a boolean to false, we need to:
Reverse the primary compressed int from the DB, using ~ (13 in the example)
Add in the absolute value of the new int 4 ( not -4), using |.
Re-reverse the full int again, using ~.
So the final code, with only one if statement:
if ( $certs_new > 0 ) {
$certs = ( $certs | $certs_new );
} else {
$certs = ~( ~$certs | abs($certs_new) );
}
// 13 and 4 .... 13
// 13 and 2 .... 15
// 13 and -4 .... 9
// 13 and -2 .... 13

Auto generated gallery with PHP

I have a minimum of 2 images and a maximum of 8 images per page. Depending on the number of images I have it's going to generate a gallery differently.
Here is a diagram of how it should work
8 - 4x4
7 - 4x3
6 - 3x3
5 - 3x2
4 - 4
3 - 3
2 - 2
The first column has the number of total images. The following numbers are images in a row.
Example
If I have 7 images, it should return an array like:
array(4,3)
The final result is that I will loop out 4 images on the first row and then 3 larger images on the second row.
If statements
I could do this with if statements but I guess there could be a way to calculate this?
Solution for everyone
If this is going to be useful for more people then it would need a function like this:
function getGalleryRows($count, $row_limit) {
}
where $count is the number of images and $row_limit is the maximum number of images there can be in a row.
I don't know if I get you right but is this what you want?
function getGalleryRows($count, $row_limit) {
$arr = array();
while ($count > 0){
array_push($arr, (($count > $row_limit) ? $row_limit : $count));
$count -= $row_limit;
}
return $arr;
}
Examples
echo json_encode(getGalleryRows(7,2)); // returns [2,2,2,1]
echo json_encode(getGalleryRows(7,4)); // returns [4,3]

PHP round up list items to nearest five

I've got a grid of 10 square list items. A bit like a gallery. If the user adds another item there will be 11. However this will look strange as the 11th item will be on its own in a new row. How can I use PHP to round up to the nearest 5 and add in the some blank/dummy list items?
You could use the modulo operator to identify the remainder of a division:
10 % 5 = 0
11 % 5 = 1
12 % 5 = 2
13 % 5 = 3
14 % 5 = 4
15 % 5 = 0
With that you can identify if (and how large) such an uncomplete row would be. Knowing how many elements are in that last uncomplete row oviously allows you to calculate the number of remaining cells to fill the row.
($y+(($y%$x)?($x-($y%$x)):0))
...where $y is the number of items(e.g. 11) and $x is the number of items in a row(e.g. 5)

Categories