V2EX = way to explore
V2EX 是一个关于分享和探索的地方
Sign Up Now
For Existing Member  Sign In
klzhoushuai
V2EX  ›  问与答

有没有大神帮忙写个正则表达式匹配字符串中的运算式

  •  
  •   klzhoushuai · Dec 18, 2020 · 1913 views
    This topic created in 1964 days ago, the information mentioned may be changed or developed.

    例:a1=1&&a2=2&&a3=3&&(a4=1||a5=2||(a6=3&&a7=4))
    匹配结果:
    array:7 [
    0 => "a1=1"
    1 => "a2=2"
    2 => "a3=3"
    3 => "a4=1"
    4 => "a5=2"
    5 => "a6=3"
    6 => "a7=4"
    ]
    例:a1==1 && a2==3 && ((a4a5+(a6+a7)/3)>10 || (a1 /((a2/100)(a2/100))) >= 28 || (a3*((a1)+(a4-a5)+(a2))>4) ) && a10==6 || a9==9 && (a3>4 || a5 <6)
    匹配结果:
    array:9 [
    0 => "a1==1"
    1 => "a2==3"
    2 => "a4a5+(a6+a7)/3>10"
    3 => "a1/((a2/100)
    (a2/100))>=28"
    4 => "a3*((a1)+(a4-a5)+(a2))>4"
    5 => "a10==6"
    6 => "a9==9"
    7 => "a3>4"
    8 => "a5<6"
    ]

    18 replies    2020-12-18 17:43:23 +08:00
    SmallTeddy
        1
    SmallTeddy  
       Dec 18, 2020
    你这个 split('&&')拆分开就行吧
    Cbdy
        2
    Cbdy  
       Dec 18, 2020 via Android
    用什么编程语言?我用 Perl 写个你能用吗?
    conanforever22
        3
    conanforever22  
       Dec 18, 2020
    如果是 shell, 用`tr`把'|&()'替换成'\n\n '然后 trim 掉前后空格就行了

    echo 'a1=1&&a2=2&&a3=3&&(a4=1||a5=2||(a6=3&&a7=4))' | tr '|&()' '\n\n '
    klzhoushuai
        4
    klzhoushuai  
    OP
       Dec 18, 2020
    @SmallTeddy
    ((a4a5+(a6+a7)/3)>10 || (a1 /((a2/100)(a2/100)))
    向这种情况就不好拆分了
    cheese
        5
    cheese  
       Dec 18, 2020
    @klzhoushuai #4 两次循环就好了,先判断&&再循环合并||。不需要正则,这个正则做还挺麻烦的
    lululau
        6
    lululau  
       Dec 18, 2020   ❤️ 1
    这个问题和正则没关系,请自行搜索“parser for mathematical expression”
    SmallTeddy
        7
    SmallTeddy  
       Dec 18, 2020
    @lululau 我觉得也和正则没关系
    klzhoushuai
        8
    klzhoushuai  
    OP
       Dec 18, 2020
    /**
    * 拆分公式
    * @param $formula
    * @return array
    */
    public function splitFormula($formula)
    {
    $formula = $t = str_replace(' ', '', $formula);
    $formula = str_replace(['&&', '||'], [';', ';'], $formula);

    $result = explode(';', $formula);

    foreach ($result as $key => $value) {
    while (true) {
    $left = substr_count($value, '(');
    $right = substr_count($value, ')');

    if ($left === $right) break;

    if ($left > $right) {
    $value = substr_replace($value, '', strpos($value, '('), 1);
    } else {
    $value = substr_replace($value, '', strrpos($value, ')'), 1);
    }
    }
    $result[$key] = $value;
    }
    return $result;
    }
    klzhoushuai
        9
    klzhoushuai  
    OP
       Dec 18, 2020
    @klzhoushuai 用了一个比较笨的方法写的
    rrfeng
        10
    rrfeng  
       Dec 18, 2020
    这个难点在括号。写个 parser 吧。
    ipwx
        11
    ipwx  
       Dec 18, 2020
    1. 没看懂楼主想干嘛。
    2. 递归文法的解析我记得超出了 regex 的范畴。
    zy445566
        12
    zy445566  
       Dec 18, 2020
    这不是正则的范畴了,这是语法解析
    gIQgxDI09Jflpdc2
        13
    gIQgxDI09Jflpdc2  
       Dec 18, 2020
    @klzhoushuai
    分两步。
    1 用正则取出 &&或者|| 符号中间的内容。
    2 利用字符串替换多余的括号。

    PHP 写法如下:
    ```
    // 两个字符串例子
    $str1 = 'a1=1&&a2=2&&a3=3&&(a4=1||a5=2||(a6=3&&a7=4))';
    $str2 = 'a1==1 && a2==3 && ((a4a5+(a6+a7)/3)>10 || (a1 /((a2/100)(a2/100))) >= 28 || (a3*((a1)+(a4-a5)+(a2))>4) ) && a10==6 || a9==9 && (a3>4 || a5 <6)';

    preg_match_all('/[^&|^\|]+/', $str1, $match);//提取 &&或者||符号中间的内容。
    if (!empty($match[0])) {
    foreach ($match[0] as &$value) {
    $value = trim(removeBrackets($value));//移除多余的括号
    }
    }

    var_dump($match);


    /**
    * 移除多余的括号
    * @param $string
    * @return string|string[]|null
    */
    function removeBrackets($string)
    {
    // 左括号 右括号 默认 0 个
    $leftBracket = $rightBracket = 0;

    // 计算括号个数
    for($i=0;$i<strlen($string);$i++){
    if ($string[$i] == '(') {
    $leftBracket++;
    } elseif ($string[$i] == ')') {
    $rightBracket++;
    }
    }
    $abs = abs($leftBracket - $rightBracket);//括号个数的差值

    if ($abs == 0) {
    return $string;
    }

    if ($leftBracket > $rightBracket) {// 如果 左括号多,移除多余的
    $string = preg_replace('/\(/', '', $string, $abs);
    } elseif ($leftBracket < $rightBracket) {// 如果 右括号多,移除多余的
    $string = preg_replace('/\)/', '', $string, $abs);
    }
    return $string;
    }
    ```
    两个例子转换结果如下:
    ```
    [
    "a1=1",
    "a2=2",
    "a3=3",
    "a4=1",
    "a5=2",
    "a6=3",
    "a7=4"
    ]
    第二个和你期待的有一点点不一样,但是我觉得应该也符合你预期。
    [
    "a1==1",
    "a2==3",
    "(a4a5+(a6+a7)/3)>10",
    "(a1 /((a2/100)(a2/100))) >= 28",
    "(a3*((a1+(a4-a5)+(a2))>4) )",
    "a10==6",
    "a9==9",
    "a3>4",
    "a5 <6"
    ]
    ```
    gIQgxDI09Jflpdc2
        14
    gIQgxDI09Jflpdc2  
       Dec 18, 2020
    为什么我的 markdown 语法没生效!排版全乱了,难受。
    谁能告诉我为什么吗?
    gIQgxDI09Jflpdc2
        15
    gIQgxDI09Jflpdc2  
       Dec 18, 2020
    原来回复还不支持 markdown 。Fine~
    gIQgxDI09Jflpdc2
        16
    gIQgxDI09Jflpdc2  
       Dec 18, 2020   ❤️ 1
    klzhoushuai
        17
    klzhoushuai  
    OP
       Dec 18, 2020
    @alex2019
    好像有点问题,如果字符串为:a1=1&&a2=2&&a3=3&&(a4=1||a5=2||(a6=3&&a7=4 &&((a8+a9)/2>50 || a10/((a1+a2)*10)>20)))
    解析出来的最后的一个表达式就有问题了吧:a10/((a1+a2*10>20)) ,正确的结果应该是:a10/((a1+a2)*10)>20
    用 $string = preg_replace('/\)/', '', $string, $abs); 匹配时这个是从左到右匹配的
    gIQgxDI09Jflpdc2
        18
    gIQgxDI09Jflpdc2  
       Dec 18, 2020
    @klzhoushuai 那你就把字符串反转之后。再调用 [去掉括号] 方法。
    About   ·   Help   ·   Advertise   ·   Blog   ·   API   ·   FAQ   ·   Solana   ·   2811 Online   Highest 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 50ms · UTC 08:17 · PVG 16:17 · LAX 01:17 · JFK 04:17
    ♥ Do have faith in what you're doing.