【问题标题】:How to match only those numbers which have an even number of `%`s preceding them?如何只匹配那些前面有偶数个 `%` 的数字?
【发布时间】:2016-11-12 11:44:13
【问题描述】:

我想捕捉出现在字符串中任意位置的数字,并将它们替换为“(.+)”。

但我只想捕获那些前面有偶数个%s 的数字。不用担心周围的字符被捕获:我们可以使用捕获组来过滤掉数字。

我无法想出一个 ECMAscript 正则表达式。

这里是游乐场:

abcd %1 %%2 %%%3 %%%%4 efgh

abcd%12%%34%%%666%%%%11efgh

成功的捕获将如下所示:


我尝试过的事情:


如果您已经意识到,第三次尝试几乎可以奏效。唯一的问题是在操场的第二行。 其实我想说的是:

如果一个数字前面有偶数个%s 并且以下任一为真,则匹配一个数字:

  • 上述整个表达式前面是 nothing [缺少(未使用或其他)字符]。
  • 上面的整个表达式前面有一个不是%的字符。

有没有办法匹配一个字符的缺席
这就是我在第三次尝试中使用\0 所做的尝试。

【问题讨论】:

标签: javascript c++ regex std ecmascript-2016


【解决方案1】:

我不知道 ECMAScript,但以下文档有答案:

ECMAScript regex

搜索负前瞻,结果如下:

(?!%)(([%]{2})*\d+)

...其中(?!%) 表示前面没有% 文字。

【讨论】:

  • (?!%) 表示% 没有跟随。您正在考虑负面的lookbehind,ECMAScript 不支持。
【解决方案2】:

问题

你想要一个负无限宽度的正则表达式:

(?<=(^|[^%])(?:%%)*)\d+

这里是.NET regex demo

在 ES7 中,不支持它,您需要使用特定于语言的方法和简化的正则表达式来匹配数字序列之前的任意数量的 %/(%*)(\d+)/g,然后在 replace 回调中检查是否百分号的数量是偶数还是非偶数,并相应地进行。

JavaScript

您可以只使用 JS 手段,而不是尝试模拟可变宽度的lookbehind:

var re = /(%*)(\d+)/g;          // Capture into Group 1 zero or more percentage signs
var str = 'abcd %1 %%2 %%%3 %%%%4 efgh<br/><br/>abcd%12%%34%%%666%%%%11efgh';
var res = str.replace(re, function(m, g1, g2) { // Use a callback inside replace
  return (g1.length % 2 === 0) ? g1 + '(.+)' : m; // If the length of the %s is even
});                             // Return Group 1 + (.+), else return the whole match
document.body.innerHTML = res;

如果数字前必须至少有 2 个%,请使用/(%+)(\d+)/g 正则表达式模式,其中%+ 至少匹配1 个(或更多)百分号。

转换为 C++

在 C++ 中可以使用相同的算法。唯一的问题是std::regex_replace 中没有对回调方法的内置支持。可以手动添加,使用方式如下:

#include <iostream>
#include <cstdlib>
#include <string>
#include <regex>
using namespace std;

template<class BidirIt, class Traits, class CharT, class UnaryFunction>
std::basic_string<CharT> regex_replace(BidirIt first, BidirIt last,
    const std::basic_regex<CharT,Traits>& re, UnaryFunction f)
{
    std::basic_string<CharT> s;

    typename std::match_results<BidirIt>::difference_type
        positionOfLastMatch = 0;
    auto endOfLastMatch = first;

    auto callback = [&](const std::match_results<BidirIt>& match)
    {
        auto positionOfThisMatch = match.position(0);
        auto diff = positionOfThisMatch - positionOfLastMatch;

        auto startOfThisMatch = endOfLastMatch;
        std::advance(startOfThisMatch, diff);

        s.append(endOfLastMatch, startOfThisMatch);
        s.append(f(match));

        auto lengthOfMatch = match.length(0);

        positionOfLastMatch = positionOfThisMatch + lengthOfMatch;

        endOfLastMatch = startOfThisMatch;
        std::advance(endOfLastMatch, lengthOfMatch);
    };

    std::sregex_iterator begin(first, last, re), end;
    std::for_each(begin, end, callback);

    s.append(endOfLastMatch, last);

    return s;
}

template<class Traits, class CharT, class UnaryFunction>
std::string regex_replace(const std::string& s,
    const std::basic_regex<CharT,Traits>& re, UnaryFunction f)
{
    return regex_replace(s.cbegin(), s.cend(), re, f);
}

std::string my_callback(const std::smatch& m) {
  if (m.str(1).length() % 2 == 0) {
    return m.str(1) + "(.+)";
  } else {
    return m.str(0);
  }
}

int main() {
    std::string s = "abcd %1 %%2 %%%3 %%%%4 efgh\n\nabcd%12%%34%%%666%%%%11efgh";
    cout << regex_replace(s, regex("(%*)(\\d+)"), my_callback) << endl;

    return 0;
}

请参阅IDEONE demo

特别感谢回调代码发到John Martin

【讨论】:

  • 没有帮助,当我说 ECMAscript 时,我是认真的。 (我正在使用 C++ 标准库)。不投反对票,因为它可能对将来有用。
  • 查看标签 - 没有 C++ 标签。虽然有 JavaScript 标签。
  • 为我辩护:它是由其他人添加的。
  • 我添加了一个 C++ 实现。
  • +1 令人兴奋!!!我表示感谢,但这个线程更多的是关于正则表达式而不是 c++/js。不过干得不错。
【解决方案3】:

您可以使用(?:[^%\d]|^|\b(?=%))(?:%%)*(\d+) 作为模式,将您的号码存储到第一个捕获组中。这也处理前面有零个 % 字符的数字。

这将匹配偶数个 % 符号,如果它们前面有:

  • 既不是 % 也不是数字(因此我们不需要捕获 % 之前的最后一个数字,因为这不适用于像 %%1%%2 这样的链)
  • 字符串的开头
  • 一个单词边界(因此任何单词字符),用于上述链

你可以看到它在行动here

【讨论】:

  • +1 是 0 被视为有效的偶数。使用你表达的想法,我想出了这个:(^|[^%\d]|[^%](?=%))(%%)*(\d+)。好吗?你能改进它吗?
  • 我已经相应地编辑了我的答案。对于提到的链,我更喜欢使用\b 而不是[^%](你的方法不会完全捕捉到它们)
  • @Wiktor 你能说明一个更新后的表达式不起作用的情况吗?
  • @AneesAhmed777:也许,它会起作用的,我收回我的话。但是,对于大多数编码人员来说,它的可读性肯定要低得多(对我来说不是,我自己想出了这个,但 Seba 是第一个 :))。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-06-07
  • 1970-01-01
相关资源
最近更新 更多