【问题标题】:How to simplify nested curly braces with regex?如何使用正则表达式简化嵌套花括号?
【发布时间】:2020-08-25 09:00:44
【问题描述】:

我尝试简化 LaTeX 中的嵌套花括号。

换句话说:{ { ... } } | { { { ... } } } |等等→{ ... }

例如有一段TeX如下:

$$\begin{aligned}
\pi &= \frac{1}{2}\sum\limits_{k = 0}^\infty {\frac{1}{{{{16}^k}}}} \left( {\frac{8}{{8k + 2}} + \frac{4}{{8k + 3}} + \frac{4}{{8k + 4}} - \frac{1}{{8k + 7}}} \right)\\
\zeta (2) &= \frac{{{\pi ^2}}}{6} = \frac{3}{{16}}\sum\limits_{k = 0}^\infty {\frac{1}{{{{64}^k}}}} \left( {\frac{{16}}{{{{(6k + 1)}^2}}} - \frac{{24}}{{{{(6k + 2)}^2}}} - \frac{8}{{{{(6k + 3)}^2}}} - \frac{6}{{{{(6k + 4)}^2}}} + \frac{1}{{{{(6k + 5)}^2}}}} \right)\\
\zeta (3) &= \frac{9}{{224}}\sum\limits_{k = 0}^\infty {\frac{1}{{{{4096}^k}}}}
\left(\begin{aligned} 
&\frac{{1024}}{{{{(24k + 2)}^3}}} - \frac{{3072}}{{{{(24k + 3)}^3}}} + \frac{{512}}{{{{(24k + 4)}^3}}} + \frac{{1024}}{{{{(24k + 6)}^3}}} + \frac{{1152}}{{{{(24k + 8)}^3}}}\\
+& \frac{{384}}{{{{(24k + 9)}^3}}} + \frac{{64}}{{{{(24k + 10)}^3}}} + \frac{{128}}{{{{(24k + 12)}^3}}} + \frac{{16}}{{{{(24k + 14)}^3}}} + \frac{{48}}{{{{(24k + 15)}^3}}} + \frac{{72}}{{{{(24k + 16)}^3}}}\\
+& \frac{{16}}{{{{(24k + 18)}^3}}} + \frac{2}{{{{(24k + 20)}^3}}} - \frac{6}{{{{(24k + 21)}^3}}} + \frac{1}{{{{(24k + 22)}^3}}}\\
\end{aligned} \right)\\
\end{aligned}$$

大括号可以嵌套,大括号之间可以有空格,但必须成对出现。

我尝试了正则表达式{ *{((?>[^{}]+|{{[^}]*}})*)} *},但它无法匹配所有情况。

如何改进我的正则表达式,或者这不能通过正则表达式完成,我必须编写一个简单的解析器?

【问题讨论】:

  • github.com/slevithan/xregexp 有一个 matchRecursive 方法,可以帮助你在这里.. js 不支持需要获得嵌套匹配的子表达式调用.. 对于最多两级嵌套,你可以使用 @ 987654328@,您可以应用两次或将其扩展为三层嵌套
  • 如果你只需要将多个{}变成单个{},使用s.replace(/{+/g, '{').replace(/}+/g, '}')
  • @WiktorStribiżew,这是错误的,例如s = '\\frac{{a}b}{c}'
  • 你用{{{{(24 + 3)}^3}}}做什么?
  • 你有问题……你用正则表达式来解决……现在你有两个问题……

标签: javascript regex


【解决方案1】:

在 JavaScript 中使用 Regex 执行此操作是一团糟。
首先,由于 LaTeX 是一种结构化文件格式,因此使用专门的解析器会更合适。
siefkenj/latex-parser 漂亮的打印机插件(基于 Michael Brade 的 LaTeX.js)可能是解决您问题的灵丹妙药。 (不过,在 JS 中也有一些 problems parsing TeX.. 但那是另一回事。)

其次,由于 JS 的 Regex 引擎不支持递归模式,事情变得更加困难。我们可以使用这样的模式(如果需要可以多次使用)来去除多余的大括号:

 \{[ ]*(\{(?:[^{}]+|(?1))*\})[ ]*\}

不幸的是,像 (?1) 递归第一个子模式或 (?R) 递归整个模式这样的语法不能在 JS 中使用。尽管如此,正如 Steven Levithan 所证明的那样

仍然,假设有一个已知的最大递归量 需要考虑,很有可能。这是解决方案 提供,它适用于 JavaScript(它不使用任何 高级正则表达式功能,实际上):

@[^{]+{(?:[^{}]|{[^{}]*})*}

但是,这仅在以下情况下有效:

  • 大括号总是平衡的,而且...
  • [...仅达到已知的最大递归级别]

使这个适应手头的问题并不是特别难,比如说两个级别的递归

{[ ]*({(?:{(?:{.*?}|.)*?}|.)*?})[ ]*}

但有一个问题:由于我们没有实际的递归模式,我们需要使用循环或辅助函数手动“递归”大括号的.replace(如下面的演示代码中所示)

const str = `\$\$\\begin{aligned}
\\pi &= \\frac{1}{2}\\sum\\limits_{k = 0}^\\infty {\\frac{1}{{{{16}^k}}}} \\left( {\\frac{8}{{8k + 2}} + \\frac{4}{{8k + 3}} + \\frac{4}{{8k + 4}} - \\frac{1}{{8k + 7}}} \\right)\\\\
\\zeta (2) &= \\frac{{{\\pi ^2}}}{6} = \\frac{3}{{16}}\\sum\\limits_{k = 0}^\\infty {\\frac{1}{{{{64}^k}}}} \\left( {\\frac{{16}}{{{{(6k + 1)}^2}}} - \\frac{{24}}{{{{(6k + 2)}^2}}} - \\frac{8}{{{{(6k + 3)}^2}}} - \\frac{6}{{{{(6k + 4)}^2}}} + \\frac{1}{{{{(6k + 5)}^2}}}} \\right)\\\\
\\zeta (3) &= \\frac{9}{{224}}\\sum\\limits_{k = 0}^\\infty {\\frac{1}{{{{4096}^k}}}}
\\left(\\begin{aligned}
&\\frac{{1024}}{{{{(24k + 2)}^3}}} - \\frac{{3072}}{{{{(24k + 3)}^3}}} + \\frac{{512}}{{{{(24k + 4)}^3}}} + \\frac{{1024}}{{{{(24k + 6)}^3}}} + \\frac{{1152}}{{{{(24k + 8)}^3}}}\\\\
+& \\frac{{384}}{{{{(24k + 9)}^3}}} + \\frac{{64}}{{{{(24k + 10)}^3}}} + \\frac{{128}}{{{{(24k + 12)}^3}}} + \\frac{{16}}{{{{(24k + 14)}^3}}} + \\frac{{48}}{{{{(24k + 15)}^3}}} + \\frac{{72}}{{{{(24k + 16)}^3}}}\\\\
+& \\frac{{16}}{{{{(24k + 18)}^3}}} + \\frac{2}{{{{(24k + 20)}^3}}} - \\frac{6}{{{{(24k + 21)}^3}}} + \\frac{1}{{{{(24k + 22)}^3}}}\\\\
\\end{aligned} \\right)\\\\
\\end{aligned}\$\$`;
const subst = `$1`;
const regex = /{[ ]*({(?:{(?:{.*?}|.)*?}|.)*?})[ ]*}/gm;

String.prototype.replacerec = function (pattern, what) {
    var newstr = this.replace(pattern, what);
    if (newstr == this)
        return newstr;
    return newstr.replace(pattern, what);
};

console.log(
  str.replacerec(regex, subst)
);

模式和递归函数的组合允许(理论上)清除任何级别的嵌套括号。如果您正在处理大文件,您可能需要使用迭代方法或按照建议使用解析器。

当我渲染修改后的 LaTeX 代码时,例如here,我得到了相同的(视觉)输出。

【讨论】:

    【解决方案2】:

    一种独立于嵌套级别的方法,使用占位符。

    let latex = `$$\\begin{aligned}
    \\pi &= \\frac{1}{2}\\sum\\limits_{k = 0}^\\infty {\\frac{1}{{{{16}^k}}}} \\left( {\\frac{8}{{8k + 2}} + \\frac{4}{{8k + 3}} + \\frac{4}{{8k + 4}} - \\frac{1}{{8k + 7}}} \\right)\\\\
    \\zeta (2) &= \\frac{{{\\pi ^2}}}{6} = \\frac{3}{{16}}\\sum\\limits_{k = 0}^\\infty {\\frac{1}{{{{64}^k}}}} \\left( {\\frac{{16}}{{{{(6k + 1)}^2}}} - \\frac{{24}}{{{{(6k + 2)}^2}}} - \\frac{8}{{{{(6k + 3)}^2}}} - \\frac{6}{{{{(6k + 4)}^2}}} + \\frac{1}{{{{(6k + 5)}^2}}}} \\right)\\\\
    \\zeta (3) &= \\frac{9}{{224}}\\sum\\limits_{k = 0}^\\infty {\\frac{1}{{{{4096}^k}}}}
    \\left(\\begin{aligned}
    &\\frac{{1024}}{{{{(24k + 2)}^3}}} - \\frac{{3072}}{{{{(24k + 3)}^3}}} + \\frac{{512}}{{{{(24k + 4)}^3}}} + \\frac{{1024}}{{{{(24k + 6)}^3}}} + \\frac{{1152}}{{{{(24k + 8)}^3}}}\\\\
    +& \\frac{{384}}{{{{(24k + 9)}^3}}} + \\frac{{64}}{{{{(24k + 10)}^3}}} + \\frac{{128}}{{{{(24k + 12)}^3}}} + \\frac{{16}}{{{{(24k + 14)}^3}}} + \\frac{{48}}{{{{(24k + 15)}^3}}} + \\frac{{72}}{{{{(24k + 16)}^3}}}\\\\
    +& \\frac{{16}}{{{{(24k + 18)}^3}}} + \\frac{2}{{{{(24k + 20)}^3}}} - \\frac{6}{{{{(24k + 21)}^3}}} + \\frac{1}{{{{(24k + 22)}^3}}}\\\\
    \\end{aligned} \\right)\\\\
    \\end{aligned}$$`;
    
    // We protect each eventual literal tilde in the string
    latex = latex.replace(/~/g, '~~');
    
    // We replace each innermost substring enclosed between curly brackets by 
    // a key. The key is used to store the matched substring in a Map.
    let substrings = new Map(),
        repNo = 1; // No of replacements initialized with a fake value to enter
                   // the loop.
    
    // The replacement pattern checks if the substring isn't a key enclosed
    // between curly brackets: in this case brackets are removed since the key
    // is a placeholder for a substring already enclosed between curly brackets.
    
    // This replacement is repeated until there's nothing to replace.    
    for (let level = 0; repNo > 0; level++) {
        repNo = 0;
        latex = latex.replace(/{(?:(~l\d+n\d+~)|[^{}]*)}/g, (m, g1) => {
            // it's a key -> remove the brackets
            if (g1) 
                return g1; 
            
            // otherwise, replace with a placeholder (the key) and
            // store the substring in the Map.
            let key = `~l${level}n${repNo++}~`;
            substrings.set(key, m);
            return key;
        });
    }
    
    // Now, we can replace each placeholder key with its value.
    
    do {
        repNo = 0;
        latex = latex.replace(/~l\d+n\d+~/g, (m) => {
            repNo++;
            return substrings.get(m);
        });
    } while (repNo);
    
    // restore the eventual escaped tildes
    latex = latex.replace(/~~/g, '~');
    
    console.log(latex);

    请注意,占位符的格式是完全任意的,请随意构建自己的。例如,不需要在其中包含级别,但是通过它您可以查看两个循环之间的字符串并查看最大嵌套级别。

    【讨论】:

      【解决方案3】:

      正则表达式是这项工作的错误工具。正则表达式可以确定正则语言,但不能确定像这样的无上下文语言(括号匹配是无上下文语言中最常见的examples 之一)。总而言之,你需要编写一个基于堆栈的解析器(下推自动机)。

      【讨论】:

      • 同意,应该是状态机。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-02-14
      • 2014-10-09
      • 1970-01-01
      • 1970-01-01
      • 2013-06-14
      相关资源
      最近更新 更多