Regex заменяет все, кроме определенного шаблона

Я хочу извлечь:

50%

Из строки, которая будет иметь более или менее этот формат:

The 50% is in here somewhere.

Я также хотел бы извлечь:

50%50%25%

Из строки, подобной этой:

50% of 50% is 25%

Regex.Match() кажется очевидным соперником. Однако это включает проверку наличия совпадений (например, match.Success), извлечение результатов из определенного индекса в массиве и/или риск обращения к индексу вне границ.

Замена Regex обычно проще применять. Одна строка выполняет задание, включая возврат результирующей строки. Это верно для многих языков.

result = Regex.Replace(input, stuffWeDontLike, "")

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

percentages = Regex.Filter("50% of 50% is 25%", "[0-9]+\%")

Можно ли сформировать регулярное выражение и инвертировать результат, как если бы это был выбор? Это позволило бы использовать регулярное выражение. Однако я не мог найти способ легко инвертировать регулярное выражение.

Как мы можем достичь желаемого результата (или похожего, соединение или так кажется приемлемым) с очень коротким и простым синтаксисом, похожим на regex replace?

Ответ 1

Вы можете использовать Regex.Matches и объединить результат каждого совпадения. Просто выберите тот, который вам больше нравится.

//Sadly, we can't extend the Regex class
public class RegExp
{
    //usage : RegExp.Filter("50% of 50% is 25%", @"[0-9]+\%")
    public static string Filter(string input, string pattern)
    {
        return Regex.Matches(input, pattern).Cast<Match>()
            .Aggregate(string.Empty, (a,m) => a += m.Value);
    }
}

public static class StringExtension
{
    //usage : "50% of 50% is 25%".Filter(@"[0-9]+\%")
    public static string Filter(this string input, string pattern)
    {
        return Regex.Matches(input, pattern).Cast<Match>()
            .Aggregate(string.Empty, (a,m) => a += m.Value);
    }
}

Ответ 2

Я не понимаю ваших аргументов, почему вы хотите использовать замену. Зачем идти в первую очередь? В классе Regex есть методы, которые позволяют точно получить все необходимые совпадения. Ваш окольный путь при достижении вашего решения я считаю бессмысленным.

Просто используйте Matches(), чтобы собрать совпадения. Затем вы можете присоединиться к ним в строку, которую вы хотели.

var str = "50% of 50% is 25%";
var re = new Regex(@"\d+%");
var ms = re.Matches(str);
var values = ms.Cast<Match>().Select(m => m.Value);
var joined = String.Join("", values); // "50%50%25%"

Ответ 3

Одним из решений является замена regex следующим образом:

Regex.Replace("50% of 50% is 25%", "(\d+\%)|(?:.+?)", "$1");

Вывод:

50%50%25%

Как общий подход:

Regex.Replace(input, (pattern)|(?:.+?), "$1");

Это находит все, что соответствует одному из следующих:

  • Шаблон. Захвачено как $1. Это то, что мы хотим сохранить.
  • Любой персонаж, любое количество раз, но не жадный. Это находит все, что не было захвачено первой группой. ?:, потому что нам не нужно захватывать эту группу.

Как заявляет MSDN: "$1 заменяет весь матч первым захваченным подвыражением". (То есть все совпадения для этой подстроки конкатенированы.)

Эффективно это описанный фильтр регулярных выражений.