查找仅出现一次的字符串中的字符

时间:2008-12-29 23:25:44

标签: php algorithm sudoku

我正在用PHP编写一个算法来解决给定的数独谜题。我已经建立了一个有点面向对象的实现,它有两个类:9x9板上每个单独瓦片的Square类,以及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,但这似乎非常低效,每个单元需要对阵列进行两次线性扫描,而在Sudoku难题中则有27个单位。

6 个答案:

答案 0 :(得分:3)

这有点像你已经排除的“非常低效”,但内置函数所以它可能非常有效:

$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”

答案 1 :(得分:1)

考虑使用二进制数代表你的“可能”,因为像AND,OR,XOR这样的二进制运算往往比字符串运算快得多。

E.g。如果正方形可以使用“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
}

我不确定这种方法是否会更快地运行,但值得研究。

答案 2 :(得分:0)

 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;

 }

那会给你每一个单身人士。获取该数组中数字的第一个和最后一个位置,如果它们匹配且不是FALSE(严格比较,如果在开始时有一个单例),那么该数组中只有一个这样的数字。

如果你在这里超级担心速度,你可以用

替换那个循环的内部
 $istr = str($i);
 if ( ($first = strpos($instring, $istr)) !== FALSE 
       and $first == $strrpos($instring, $istr) ) $results[] = $i;

用于最小数量的计算。好吧,假设PHP的原生strpos是处理这些事情的最佳方式,据我所知这并非不合理。

答案 3 :(得分:0)

我最后一次骗过Sudoku解决,我有一个名为“Run”的第三个类。为每行col和3x3 square创建一个Run实例。每个方块都有三个与之相关的运行。 Run类包含尚未放置在运行中的数字集。然后解决板然后迭代地在每个方格处交叉集合。这样可以处理80%的大多数中型板和60%的大多数硬板。一旦你完成整个电路板而没有任何变化,你就可以继续使用更高级别的逻辑。每当你的高级逻辑填充一个正方形时,你再次穿过正方形。

关于此设置的好处是您可以轻松地向变换器添加变体。假设您使用两个对角线也是唯一的变体。你只需要为这18个方格添加第4轮。

答案 4 :(得分:0)

我会做的,实际上是使用二进制位来存储实际值作为建议的另一个答案。这允许进行有效的检查,并且通常可以将Sudoku本身借给更多数学(=有效且更短)的解决方案(仅仅是我的印象,我还没有研究过这个)。

基本上,你用正方形表示数字而不是数字,但功率为2

"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

答案 5 :(得分:0)

这是一种仅使用PHP内置函数的方法,它应该非常快。

function getUniques($sNumbers)
{
    return join(array_keys(array_count_values(str_split($sNumbers)),1));
}

echo getUniques("1234567891234"); // return 56789;