V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX 提问指南
klzhoushuai
V2EX  ›  问与答

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

  •  
  •   klzhoushuai · 2020-12-18 10:42:40 +08:00 · 1415 次点击
    这是一个创建于 1215 天前的主题,其中的信息可能已经有所发展或是发生改变。

    例: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 条回复    2020-12-18 17:43:23 +08:00
    SmallTeddy
        1
    SmallTeddy  
       2020-12-18 11:10:24 +08:00
    你这个 split('&&')拆分开就行吧
    Cbdy
        2
    Cbdy  
       2020-12-18 11:41:40 +08:00 via Android
    用什么编程语言?我用 Perl 写个你能用吗?
    conanforever22
        3
    conanforever22  
       2020-12-18 13:10:05 +08:00
    如果是 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
       2020-12-18 13:10:58 +08:00
    @SmallTeddy
    ((a4a5+(a6+a7)/3)>10 || (a1 /((a2/100)(a2/100)))
    向这种情况就不好拆分了
    cheese
        5
    cheese  
       2020-12-18 13:14:41 +08:00
    @klzhoushuai #4 两次循环就好了,先判断&&再循环合并||。不需要正则,这个正则做还挺麻烦的
    lululau
        6
    lululau  
       2020-12-18 13:23:28 +08:00   ❤️ 1
    这个问题和正则没关系,请自行搜索“parser for mathematical expression”
    SmallTeddy
        7
    SmallTeddy  
       2020-12-18 13:34:56 +08:00
    @lululau 我觉得也和正则没关系
    klzhoushuai
        8
    klzhoushuai  
    OP
       2020-12-18 14:02:46 +08:00
    /**
    * 拆分公式
    * @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
       2020-12-18 14:03:38 +08:00
    @klzhoushuai 用了一个比较笨的方法写的
    rrfeng
        10
    rrfeng  
       2020-12-18 14:04:53 +08:00
    这个难点在括号。写个 parser 吧。
    ipwx
        11
    ipwx  
       2020-12-18 14:07:00 +08:00
    1. 没看懂楼主想干嘛。
    2. 递归文法的解析我记得超出了 regex 的范畴。
    zy445566
        12
    zy445566  
       2020-12-18 14:29:43 +08:00
    这不是正则的范畴了,这是语法解析
    alex2019
        13
    alex2019  
       2020-12-18 15:53:17 +08:00
    @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"
    ]
    ```
    alex2019
        14
    alex2019  
       2020-12-18 15:54:32 +08:00
    为什么我的 markdown 语法没生效!排版全乱了,难受。
    谁能告诉我为什么吗?
    alex2019
        15
    alex2019  
       2020-12-18 15:59:43 +08:00
    原来回复还不支持 markdown 。Fine~
    alex2019
        16
    alex2019  
       2020-12-18 16:19:28 +08:00   ❤️ 1
    klzhoushuai
        17
    klzhoushuai  
    OP
       2020-12-18 17:15:27 +08:00
    @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); 匹配时这个是从左到右匹配的
    alex2019
        18
    alex2019  
       2020-12-18 17:43:23 +08:00
    @klzhoushuai 那你就把字符串反转之后。再调用 [去掉括号] 方法。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   3413 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 32ms · UTC 12:10 · PVG 20:10 · LAX 05:10 · JFK 08:10
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.