【问题标题】:Building a regex from a set of logical expressions从一组逻辑表达式构建正则表达式
【发布时间】:2014-06-06 01:21:36
【问题描述】:

我为逻辑表达式创建了一个简单的语法,使用布尔运算符和字符串作为它们的操作数,我计划将其作为 URL 查询字符串传递。我将根据$query 字符串中的操作数是否存在于另一个名为$text 的字符串中来使用它来匹配字符串。

考虑以下示例(在 PHP 中):

function search($text, $query) // $text, $query are strings
{
   // returns true if logical expression built from $query is true for $test string
   // otherwise returns false. $query explained:
   // (1) variables in $query are strings between brackets and operators
   // (2) operators are: &=and, |=or, !=not 
   // (3) priority of logical operations is defined by brackets: ()
   // (4) variable = true if substring exists in $text, false if not
}

// examples:
$text1 = 'str4 str3 blalbablastr2';
$text2 = 'str4whateverhere str3str5';

// str5 is present or str1 is present
$simpleQuery  = 'str5|str1';

// (str1 or str2 present), (str3 present), and (str4 present or str5 not present)
$complexQuery = '(str1|str2)&str3&(str4|!str5)';

search($text1, $simpleQuery);  // returns false as 'str5' OR 'str1' = false
search($text2, $simpleQuery);  // returns true  as 'str5' = true
search($text1, $complexQuery); // returns true  as 'str2' AND 'str3' AND ('str4' AND NOT 'str5') = true
search($text2, $complexQuery); // returns false as 'str1' OR 'str2' = false

我可以使用“传统”方法编写搜索功能:

  • 提取子字符串并创建相关变量
  • 初始化变量(根据它们在 $text 中的存在分配真/假)
  • 构建操作树(优先级)
  • 将运算符应用于变量

但是我相信使用正则表达式的方法更短、更快。如果您对正则表达式足够了解以提供实际帮助,请提供帮助。谢谢!

【问题讨论】:

  • 您调用的search() 函数是什么? search 不是内置的 PHP 函数。你的意思是preg_match
  • (str4|!str5) 是指 str4 还是不是 str5?所以有什么不是str5
  • @vvanasten OP 询问如何编写search() 函数
  • @AbraCadaver 我认为 OP 的意思是“字符串包含'str4'或不包含'str5'”
  • 差不多就是正则表达式,为什么不用正则表达式呢?

标签: php regex search


【解决方案1】:

如果您只想匹配字符串,您可能不需要编写search 函数。 preg_match 执行您示例中的搜索功能。

如果您不了解正则表达式的工作原理,请访问http://www.regular-expressions.info/

【讨论】:

  • 如果我每十年使用一次正则表达式会怎样?无论如何我应该学习它吗? ) 我来这里是为了获得更多实用和快速的帮助。还是谢谢。
  • @user2204231 如果您实际上每 10 年使用一次正则表达式,那么不会。但您肯定会发现它们很有用,而且使用频率更高。
【解决方案2】:

您正在寻找一种绕过“传统”方法的方法,但即使使用正则表达式方法,您仍然需要执行您概述的大部分步骤:

  • 您仍然需要在构建正则表达式之前提取子字符串
  • 您仍需要确定正确的操作顺序

如果您根本不使用语法并将过滤器存储为正则表达式,我认为您只会从正则表达式方法中获得优势。

下面是一些正则表达式的示例,它们会产生与过滤器相同的结果:

# simpleQuery
str5|str1

# complexQuery (with lookahead)
(?=.*(str1|str2))
(?=.*str3)
((?=.*str4)|(?!.*str5))

当您开始添加连词时(如在complexQuery 中),您最终必须明确涵盖搜索词的不同顺序,我已经使用lookaheads 处理了这一点。

用简单的语言来说,complexQuery 中的前瞻实际上只是说明搜索字符串中存在某个位置,即:

  • 后跟以“str1”或“str2”结尾的任意数量的字符
  • 后跟任意数量的以“str3”结尾的字符
  • 要么:
    • 后跟以“str4”结尾的任意数量的字符
    • 后面不能跟任意数量的以“str5”结尾的字符

如果没有前瞻,complexQuery 的前两行必须写成:

((str1|str2).*str3)|(str3.*(str1|str2))

当您添加更多连词时,第二种方法将变得非常笨拙。

如您所见,正则表达式远不如您的语法漂亮,我认为您的原始方法可能是最好的。

【讨论】:

  • 关于效率的说明:这些前瞻涉及回溯,这将导致搜索字符串为每次前瞻解析一次,因此如果您愿意,您绝对可以编写更高效的算法。查看regex101.com 的正则表达式调试器,了解将text1complexQuery 匹配所需的93 个步骤!
  • 感谢您的宝贵意见!请参阅我的答案 - 它使用 eval 但有效。
【解决方案3】:

这就是我使用 eval() 解决我自己的问题的方式。 也许不是最好的解决方案(由于 eval),但它确实有效。

注意:为了让生活更轻松,我将 $query 中的子替换与周围的引号区分开来。

class QueryText
{
/**
 * Parses $query and performs search in $text
 *
 * @param  string $query   query string, PHP syntax, search strings should be in commas
 * @param  string $text    content to searhc
 * @param  array $options
 *
 * @return mixed          true on success,
 *                        false on failure,
 *                        null if parsing error
 *
 */
public function search($query, $text, $options)
{
    $defaultOptions =
    [
        'caseSensitive' => false
    ];

    $options = array_merge($defaultOptions, $options);

    $text  = ($options['caseSensitive']) ? $text : mb_strtolower($text);
    $query = ($options['caseSensitive']) ? $query : mb_strtolower($query);

    if (!self::isValid($query, $text)) return null;

    $evalStr = self::parse($query, $text);

    // prepare for execution
    $evalStr = 'return ((' . $evalStr . ') ? 1 : 0);';
    $result = eval($evalStr);

    return ($result===false) ? null : (($result===1) ? true : false);
}

private function isValid($query, $text)
{
    // 1. check general syntax: should be any strings in quotes; left symbols should be &&, ||, (, ), !, spaces
    $regex = '/("[^"\']+")|(\'[^\'"]+\')|(&&)|(\|\|)|([!()\s])/';
    $str   = preg_replace($regex, '', $query);
    if ($str!=='') return false;

    // 2. check brackets: should be even
    $regex   = '/([()])/';
    $matches = [];
    preg_match_all($regex, $query, $matches);
    if (count($matches[0])%2 !== 0) return false;

    // 3. check parsed expression
    $regex   = '/(\|&)|(&\|)|(&\))|(\(&)|(\(\|)|(\|\))|(&&&)|(\|\|\|)|(!\))|(^&)|(^\|)|(^\))|(&$)|(\|$)|(!$)|(\($)/';
    $str     = self::parse($query, $text);
    $matches = [];
    preg_match_all($regex, $str, $matches);
    foreach($matches as $match)
        if (count($match)!==0) return false;

    return true;
}

private function parse($query, $text)
{
    // inject true/false instead of strings
    $regex = '/("[^"\']+")|(\'[^\'"]+\')/';
    $evalStr = preg_replace_callback(
        $regex,
        function($matches) use($text)
        {
            if (trim($matches[0])=='') return '';
            $str = $matches[0];
            $str = trim($str);          // remove spaces
            $str = substr($str, 1);     // remove left quote
            $str = substr($str, 0, -1); // remove right quote
            if (strpos(trim($text), $str) !== false) return 'true';
            return 'false';
        },
        $query);

    // remove spaces and return result
    $regex = '/([\s]+)/';
    return preg_replace($regex, '', $evalStr);
}
}

【讨论】:

    【解决方案4】:

    你可以看这里 http://tr1.php.net/preg_match http://tr1.php.net/preg_match_all

    if (preg_match("/php/i", "PHP is the web scripting language of choice.")) {
    echo "A match was found.";
    } else {
    echo "A match was not found.";
    }
    

    【讨论】:

    • 虽然preg_match 可以用于解决 OP 的问题,但这个答案有点含糊,并没有回答问题的困难部分,即如何从输入格式转换为正确的正则表达式可以传递给preg_match
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多