【问题标题】:Generate all possible matches for regex pattern in PHP在 PHP 中为正则表达式模式生成所有可能的匹配项
【发布时间】:2021-06-11 05:49:35
【问题描述】:

关于 SO 有很多问题询问如何解析正则表达式模式并输出该模式的所有可能匹配项。但是,出于某种原因,我能找到的每一个(1234567,可能更多)都用于 Java或某种类型的 C(只有一种用于 JavaScript),我目前需要在 PHP 中执行此操作。

我已经用谷歌搜索到了我心中的 (dis) 内容,但无论我做什么,谷歌给我的几乎唯一的东西就是指向 preg_match() 的文档的链接以及关于如何使用正则表达式的页面,这是相反的我想要的。

我的正则表达式模式都非常简单并且保证是有限的;唯一使用的语法是:

  • [] 用于字符类
  • () 用于子组(不需要捕获)
  • |(管道)用于子组内的替代匹配
  • ? 用于零或一匹配

所以一个例子可能是[ct]hun(k|der)(s|ed|ing)?来匹配动词chunkthunkchunderthunder的所有可能形式,共有十六种排列。

理想情况下,应该有一个用于 PHP 的库或工具,它将遍历(有限)正则表达式模式并输出所有可能的匹配项,一切就绪。有谁知道这样的库/工具是否已经存在?

如果不是,那么制作一个优化的方法是什么? This answer for JavaScript 是我能找到的最接近我应该能够适应的东西,但不幸的是我无法理解它的实际工作原理,这使得适应它变得更加棘手。另外,无论如何,在 PHP 中可能有更好的方法。一些关于如何最好地分解任务的逻辑指针将不胜感激。

编辑:由于显然不清楚这在实践中会是什么样子,所以我正在寻找允许这种类型输入的东西:

$possibleMatches = parseRegexPattern('[ct]hun(k|der)(s|ed|ing)?');

– 然后打印$possibleMatches 应该会给出这样的结果(在我的情况下,元素的顺序并不重要):

Array
(
    [0] => chunk
    [1] => thunk
    [2] => chunks
    [3] => thunks
    [4] => chunked
    [5] => thunked
    [6] => chunking
    [7] => thunking
    [8] => chunder
    [9] => thunder
    [10] => chunders
    [11] => thunders
    [12] => chundered
    [13] => thundered
    [14] => chundering
    [15] => thundering
)

【问题讨论】:

  • 那么,你不想匹配模式,你想看看模式会匹配哪些“单词”?
  • @Steven 是的,没错。我基本上想采用一个模式并将其转换为模式可能匹配的所有字符串的非正则表达式列表。
  • 好的,大概这仅适用于字符类有限的正则表达式?显然,任何带有 . 或量词(例如 +)的东西都可能意味着相当长的列表!
  • @Steven 是的,这就是为什么我指定我的模式都是有限的(即,它们不包含任何可能使匹配字符串列表无限的东西)——没有使用正则表达式语法从问题中列出的四个中。
  • 好的,如果是这种情况,您可以简单地去掉 变量组,根据需要将它们拆分(字符类按字母,捕获组按单词),然后在每个级别使用递归来完成?

标签: php regex parsing


【解决方案1】:

方法

  1. 你需要去掉变量模式;您可以使用preg_match_all 来执行此操作

    preg_match_all("/(\[\w+\]|\([\w|]+\))/", '[ct]hun(k|der)(s|ed|ing)?', $matches);
    
    /* Regex:
    
    /(\[\w+\]|\([\w|]+\))/
    /                       : Pattern delimiter
     (                      : Start of capture group
      \[\w+\]               : Character class pattern
             |              : OR operator
              \([\w|]+\)    : Capture group pattern
                        )   : End of capture group
                         /  : Pattern delimiter
    
    */
    
  2. 然后您可以将捕获组扩展为字母或单词(取决于类型)

    $array = str_split($cleanString, 1); // For a character class
    $array = explode("|", $cleanString); // For a capture group
    
  3. 递归处理每个$array

代码

function printMatches($pattern, $array, $matchPattern)
{
    $currentArray = array_shift($array);

    foreach ($currentArray as $option) {
        $patternModified = preg_replace($matchPattern, $option, $pattern, 1);
        if (!count($array)) {
            echo $patternModified, PHP_EOL;
        } else {
            printMatches($patternModified, $array, $matchPattern);
        }
    }
}

function prepOptions($matches)
{
    foreach ($matches as $match) {
        $cleanString = preg_replace("/[\[\]\(\)\?]/", "", $match);
        
        if ($match[0] === "[") {
            $array = str_split($cleanString, 1);
        } elseif ($match[0] === "(") {
            $array = explode("|", $cleanString);
        }
        if ($match[-1] === "?") {
            $array[] = "";
        }
        $possibilites[] = $array;
    }
    return $possibilites;
}

$regex        = '[ct]hun(k|der)(s|ed|ing)?';
$matchPattern = "/(\[\w+\]|\([\w|]+\))\??/";

preg_match_all($matchPattern, $regex, $matches);

printMatches(
    $regex,
    prepOptions($matches[0]),
    $matchPattern
);

附加功能

扩展嵌套组

在使用中,您可以将它放在“preg_match_all”之前。

$regex        = 'This happen(s|ed) to (be(come)?|hav(e|ing)) test case 1?';

echo preg_replace_callback("/(\(|\|)(\w+)(?:\(([\w\|]+)\)\??)/", function($array){
    $output = explode("|", $array[3]);
    if ($array[0][-1] === "?") {
        $output[] = "";
    }
    foreach ($output as &$option) {
        $option = $array[2] . $option;
    }
    return $array[1] . implode("|", $output);
}, $regex), PHP_EOL;

输出:

This happen(s|ed) to (become|be|have|having) test case 1?

匹配单个字母

主要是更新正则表达式:

$matchPattern = "/(?:(\[\w+\]|\([\w|]+\))\??|(\w\?))/";

并将else 添加到prepOptions 函数:

} else {
    $array = [$cleanString];
}

完整的工作示例

function printMatches($pattern, $array, $matchPattern)
{
    $currentArray = array_shift($array);

    foreach ($currentArray as $option) {
        $patternModified = preg_replace($matchPattern, $option, $pattern, 1);
        if (!count($array)) {
            echo $patternModified, PHP_EOL;
        } else {
            printMatches($patternModified, $array, $matchPattern);
        }
    }
}

function prepOptions($matches)
{
    foreach ($matches as $match) {
        $cleanString = preg_replace("/[\[\]\(\)\?]/", "", $match);
        
        if ($match[0] === "[") {
            $array = str_split($cleanString, 1);
        } elseif ($match[0] === "(") {
            $array = explode("|", $cleanString);
        } else {
            $array = [$cleanString];
        }
        if ($match[-1] === "?") {
            $array[] = "";
        }
        $possibilites[] = $array;
    }
    return $possibilites;
}

$regex        = 'This happen(s|ed) to (be(come)?|hav(e|ing)) test case 1?';
$matchPattern = "/(?:(\[\w+\]|\([\w|]+\))\??|(\w\?))/";

$regex = preg_replace_callback("/(\(|\|)(\w+)(?:\(([\w\|]+)\)\??)/", function($array){
    $output = explode("|", $array[3]);
    if ($array[0][-1] === "?") {
        $output[] = "";
    }
    foreach ($output as &$option) {
        $option = $array[2] . $option;
    }
    return $array[1] . implode("|", $output);
}, $regex);


preg_match_all($matchPattern, $regex, $matches);

printMatches(
    $regex,
    prepOptions($matches[0]),
    $matchPattern
);

输出:

This happens to become test case 1
This happens to become test case 
This happens to be test case 1
This happens to be test case 
This happens to have test case 1
This happens to have test case 
This happens to having test case 1
This happens to having test case 
This happened to become test case 1
This happened to become test case 
This happened to be test case 1
This happened to be test case 
This happened to have test case 1
This happened to have test case 
This happened to having test case 1
This happened to having test case 

【讨论】:

  • 更新为包含? 功能。
  • 这非常巧妙……我需要更仔细地阅读它,才能在我的脑海中正确地映射出内部运作,但我明白了流程的要点。但不幸的是,它不适用于嵌套子组,这也是我最难以弄清楚的部分。我尝试了一些匹配模式的排列(如/(\[\w+\]|\(((?>[^()]+)|(?R))\))\??/),但都没有奏效。我想知道是否有可能通过一次preg_match_all() 电话获得所有信息。举一个带有嵌套子匹配的模式的实际示例,我有andagts(bog(en)?|bøger(ne)?)s?
  • 这是真的,至少对于无限递归。在实践中,递归最多不会超过三个级别,因此仍然是有限的,尽管仍然比没有它更复杂。我确实可以控制源,所以也许我应该通过它并避免嵌套子组。无论如何,它们中的那么多,它们可以很容易地变成非嵌套的替代品......
  • 您总是可以扩展嵌套组吗?类似:echo preg_replace( "/(\(|\|)(\w+)\((\w+)\)\?/", "$1$2|$2$3", $pattern );(这个 sn-p 还假设嵌套组始终采用(...)? 的形式)
  • 我最终只是删除了嵌套组——总共只有十几个(在大约 2,000 个模式中)。虽然此方法不会将 ? 扩展为带有和不带有前面实体的变体,并且在某些情况下它不会按预期扩展,但它对于我的目的来说已经足够好了。目标是为使用插件 IndexMatic 生成的 InDesign 文档中的索引创建一个查询列表文件。 IndexMatic 确实支持正则表达式,但它在我的查询列表文件中卡住了,可能是因为 太多 正则表达式太复杂了。但是这个方法的输出是有效的。
猜你喜欢
  • 2011-10-02
  • 1970-01-01
  • 2015-05-05
  • 1970-01-01
  • 1970-01-01
  • 2010-10-12
  • 2013-01-25
  • 1970-01-01
  • 2015-07-11
相关资源
最近更新 更多