Как сопоставить только те числа, перед которыми стоит четное число «%»?

Я хочу поймать числа, появляющиеся в любом месте строки, и заменить их на "(.+)".

Но я хочу поймать только те числа, которым предшествует четное число %. Не беспокойтесь, если какие-либо окружающие символы застрянут: мы можем использовать группы захвата для фильтрации чисел.

Я не могу придумать регулярное выражение ECMAscript.

Вот детская площадка:

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

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

Успешный перехват будет вести себя следующим образом:
желаемое поведение


Что я пробовал:

попытка 1

попытка 2

попытка 3


Если вы поняли, третья попытка почти работает. Единственные проблемы на второй линии детской площадки. Собственно, что я хотел сказать этим выражением:

Совпадение с числом, если ему предшествует четное число % и выполняется одно из следующих условий:

  • Приведенному выше целому выражению предшествует ничего [отсутствие (неиспользованного или иного) символа].
  • Приведенному выше целому выражению предшествует символ, отличный от %.

Есть ли способ сопоставить отсутствие символа?
Это то, что я пытался сделать, используя \0 в третьей попытке.


person AneesAhmed777    schedule 10.07.2016    source источник
comment
Я не знаком с ECMAscript. Разве это не позволяет смотреть назад?   -  person Holloway    schedule 10.07.2016
comment
судя по изображению, вы используете Sublime Text..   -  person rock321987    schedule 10.07.2016
comment
возможный дубликат Javascript: эквивалент отрицательного просмотра назад? (здесь ничего не изменилось с ES7)   -  person Bergi    schedule 10.07.2016


Ответы (3)


Вы можете использовать (?:[^%\d]|^|\b(?=%))(?:%%)*(\d+) в качестве шаблона, где ваш номер сохраняется в первой группе захвата. Это также обрабатывает числа, которым предшествует ноль символов %.

Это будет соответствовать четному количеству знаков %, если им предшествует:

  • ни %, ни число (поэтому нам не нужно перехватывать последнее число перед %, так как это не будет работать с цепочками типа %%1%%2)
  • начало строки
  • граница слова (таким образом, любой символ слова) для цепочек, упомянутых выше

Вы можете увидеть его в действии здесь

person Sebastian Proske    schedule 10.07.2016
comment
+1 Да 0 следует считать допустимым четным числом. Используя идеи из вашего выражения, я придумал это: (^|[^%\d]|[^%](?=%))(%%)*(\d+). Это хорошо? Можете ли вы улучшить его? - person AneesAhmed777; 10.07.2016
comment
Я отредактировал свой ответ соответственно. Я бы предпочел использовать \b вместо [^%] для упомянутых цепочек (вы не поймаете их полностью с вашим подходом) - person Sebastian Proske; 10.07.2016
comment
@Wiktor Можете ли вы показать случай, когда обновленное выражение не работает? - person AneesAhmed777; 11.07.2016
comment
@ AneesAhmed777: Возможно, это сработает, беру свои слова обратно. Однако для большинства кодеров он определенно гораздо менее читабелен (не для меня, я бы сам это придумал, но Себа был первым :) ). - person Wiktor Stribiżew; 11.07.2016

Проблема

Вам нужно регулярное выражение с отрицательным просмотром бесконечной ширины:

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

Вот демонстрация регулярного выражения .NET

В ES7 это не поддерживается, вам нужно использовать языковые средства и упрощенное регулярное выражение, чтобы сопоставить любое количество % перед последовательностью цифр: /(%*)(\d+)/g, а затем проверить внутри обратного вызова replace, является ли количество знаков процента четным или нет, и действовать соответственно.

JavaScript

Вместо того, чтобы пытаться эмулировать просмотр назад с переменной шириной, вы можете просто использовать средства JS:

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++. Единственная проблема заключается в том, что внутри файла 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.

Выражаем особую благодарность за код обратного вызова Джону Мартину.

person Wiktor Stribiżew    schedule 10.07.2016
comment
Бесполезно, когда я говорю ECMAscript, я имею в виду это. (Я использую стандартную библиотеку С++). Не понижаю голосование, потому что это может быть полезно кому-то в будущем. - person AneesAhmed777; 10.07.2016
comment
Посмотрите на теги - тега С++ нет. Однако есть тег JavaScript. - person Wiktor Stribiżew; 10.07.2016
comment
В мою защиту: это было добавлено кем-то другим. - person AneesAhmed777; 10.07.2016
comment
Я добавил реализацию C++. - person Wiktor Stribiżew; 10.07.2016
comment
+1 Сногсшибательно!!! Я выражаю свою благодарность, но эта ветка больше о регулярных выражениях, чем о C++/js. Хорошая работа, хотя. - person AneesAhmed777; 11.07.2016
comment
Ну, регулярное выражение часто идет рука об руку с кодом вокруг него. Если вы можете позволить себе еще пару строк кода, которые помогут упростить регулярное выражение, это обычно очень помогает всем тем разработчикам, которые собираются поддерживать код после того, как вы покинете компанию. Не так много людей разбираются в границах слов с опережением. Многие даже допускают ошибки при написании классов персонажей. - person Wiktor Stribiżew; 11.07.2016
comment
За последние несколько часов я понял практичность вашего метода. Итак, я использую его в своем коде. Его легко масштабировать для решения любой задачи, в отличие от сложного регулярного выражения, о котором я просил. Но не могу принять в качестве ответа, потому что ответ @Sebastian более подходит для моей ограниченной проблемы. Но для общей и неограниченной проблемы ваше решение великолепно. :-) - person AneesAhmed777; 11.07.2016
comment
Как хочешь. Принимайте только те ответы, которые лучше всего подходят для вас, и это субъективно - как и все здесь, на SO. Удачного кодирования! - person Wiktor Stribiżew; 11.07.2016

Я не знаю ECMAScript, но в следующей документации есть ответ:

Регулярное выражение ECMAScript

Найдите отрицательный просмотр вперед, что приведет к чему-то вроде этого:

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

...где (?!%) означает, что не предшествует литерал %.

person Ruben Pirotte    schedule 10.07.2016
comment
(?!%) означает, что за ним не следует %. Вы думаете об отрицательном lookbehind, который не поддерживается в ECMAScript. - person Alan Moore; 10.07.2016