【问题标题】:Find circular replacement patterns查找循环替换模式
【发布时间】:2014-09-30 21:37:03
【问题描述】:

假设我想通过替换字典中的占位符来扩展字符串。替换字符串也可以包含占位符:

$pattern = "#a# #b#";
$dict = array("a" => "foo", "b" => "bar #c#", "c" => "baz");
while($match_count = preg_match_all('/#([^#])+#/', $pattern, $matches)) {
    for($i=0; $i<$match_count; $i++) {
        $key = $matches[1][$i];
        if(!isset($dict[$key])) { throw new Exception("'$key' not found!"); }
        $pattern = str_replace($matches[0][$i], $dict[$key], $pattern); 
     }   
}
echo $pattern;

只要没有循环替换模式,就可以正常工作,例如"c" =&gt; "#b#"。然后程序就会陷入死循环,直到内存耗尽。

有没有一种简单的方法来检测这种模式?我正在寻找一种解决方案,其中替换之间的距离可以任意长,例如。 a->b->c->d->f->a
理想情况下,解决方案也将在循环中发生,而不是单独分析。

【问题讨论】:

  • 由于您的替换规则集本质上是一个图,您可以应用 DFS 或对其进行拓扑排序以检测循环。

标签: php regex replace cycle-detection


【解决方案1】:

单字符键

如果键是单个字符,这很容易:只需检查值侧的字符串是否包含作为键的字符。

foreach ($your_array as $key => $value) {
    foreach(str_split($value) as $ch) {
        if(array_key_exists ($ch,$your_array) {
            #Problem, cycle is possible
        }
    }
}
#We're fine

现在即使有循环,这并不意味着它会在每个字符串上触发(例如在空字符串中,不会触发任何模式,因此不会触发循环)。在这种情况下,您可以将其合并到您的检查器中:如果第二次触发规则,则会出现问题。只是因为如果是这样的话,之前的模式已经为此产生了一个契机,所以这个契机会一次又一次地产生。

字符串键

如果键也是字符串,这可能是 Post Correspondence Problem 无法确定...

【讨论】:

    【解决方案2】:

    感谢 georg 和 this post 的评论,我想出了一个将模式转换为图形并使用拓扑排序来检查循环替换的解决方案。

    这是我的解决方案:

    $dict = array("a" => "foo", "b" => "bar #c#", "c" => "baz #b#");
    
    # Store incoming and outgoing "connections" for each key => pattern replacement
    $nodes = array();
    foreach($dict as $patternName => $pattern) {
        if (!isset($nodes[$patternName])) {
            $nodes[$patternName] = array("in" => array(), "out" => array());
        }
        $match_count = preg_match_all('/#([^#])+#/', $pattern, $matches);
        for ($i=0; $i<$match_count; $i++) {
            $key = $matches[1][$i];
            if (!isset($dict[$key])) { throw new Exception("'$key' not found!"); }
            if (!isset($nodes[$key])) {
                $nodes[$key] = array("in" => array(), "out" => array());
            }
            $nodes[$key]["in"][]          = $patternName;
            $nodes[$patternName]["out"][] = $key;
         }   
    }
    # collect leaf nodes (no incoming connections)
    $leafNodes = array();
    foreach ($nodes as $key => $connections) {
        if (empty($connections["in"])) {
            $leafNodes[] = $key;
        }
    }
    # Remove leaf nodes until none are left
    while (!empty($leafNodes)) {
        $nodeID = array_shift($leafNodes);
        foreach ($nodes[$nodeID]["out"] as $outNode) {
            $nodes[$outNode]['in'] = array_diff($nodes[$outNode]['in'], array($nodeID));
            if (empty($nodes[$outNode]['in'])) {
                $leafNodes[] = $outNode;
            }
        }
        $nodes[$nodeID]['out'] = array();
    }
    # Check for non-leaf nodes. If any are left, there is a circular pattern
    foreach ($nodes as $key => $node) {
        if (!empty($node["in"]) || !empty($node["out"]) ) {
            throw new Exception("Circular replacement pattern for '$key'!");
        }
    }
    
    # Now we can safely do replacement 
    $pattern = "#a# #b#";
    while ($match_count = preg_match_all('/#([^#])+#/', $pattern, $matches)) {
        $key = $matches[1][$i];
        $pattern = str_replace($matches[0][$i], $dict[$key], $pattern); 
    }
    echo $pattern;
    

    【讨论】:

      猜你喜欢
      • 2019-10-23
      • 2021-02-12
      • 2021-11-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-01-03
      • 2016-05-11
      相关资源
      最近更新 更多