Найти последнее совпадение с Java regex matcher - PullRequest
19 голосов
/ 21 июня 2011

Я пытаюсь получить последний результат матча без необходимости циклически перебирать .find ()

Вот мой код:

String in = "num 123 num 1 num 698 num 19238 num 2134";
Pattern p = Pattern.compile("num '([0-9]+) ");
Matcher m = p.matcher(in);

if (m.find()) {
     in = m.group(1);
}

Это даст мне первый результат,Как найти последнее совпадение, не просматривая потенциально огромный список?

Ответы [ 10 ]

18 голосов
/ 21 июня 2011

Вы можете добавить .* к своему регулярному выражению, что жадно потребляет все символы до последнего совпадения:

import java.util.regex.*;

class Test {
  public static void main (String[] args) {
    String in = "num 123 num 1 num 698 num 19238 num 2134";
    Pattern p = Pattern.compile(".*num ([0-9]+)");
    Matcher m = p.matcher(in);
    if(m.find()) {
      System.out.println(m.group(1));
    }
  }
}

Печать:

2134

Вы также можете перевернуть строку, а также изменить свое регулярное выражение вместо обратного:

import java.util.regex.*;

class Test {
  public static void main (String[] args) {
    String in = "num 123 num 1 num 698 num 19238 num 2134";
    Pattern p = Pattern.compile("([0-9]+) mun");
    Matcher m = p.matcher(new StringBuilder(in).reverse());
    if(m.find()) {
      System.out.println(new StringBuilder(m.group(1)).reverse());
    }
  }
}

Но ни одно из решений не лучше, чем просто циклически проходить все матчи, используя while (m.find()), IMO.

14 голосов
/ 25 апреля 2013

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

String in = "num 123 num 1 num 698 num 19238 num 2134";
Pattern p = Pattern.compile("num '([0-9]+) ");
Matcher m = p.matcher(in);
if (m.find()) {
  in= m.group(m.groupCount());
}
5 голосов
/ 21 июня 2011

Почему бы не сделать это простым?

in.replaceAll(".*[^\\d](\\d+).*", "$1")
3 голосов
/ 21 июня 2011

Java не предоставляет такой механизм.Единственное, что я могу предложить, - это двоичный поиск последнего индекса.

Это будет примерно так:

N = haystack.length();
if ( matcher.find(N/2) ) {
    recursively try right side
else
    recursively try left side

Edit

А вот код, который делаетэто потому, что я обнаружил, что это интересная проблема:

import org.junit.Test;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static org.junit.Assert.assertEquals;

public class RecursiveFind {
    @Test
    public void testFindLastIndexOf() {
        assertEquals(0, findLastIndexOf("abcdddddd", "abc"));
        assertEquals(1, findLastIndexOf("dabcdddddd", "abc"));
        assertEquals(4, findLastIndexOf("aaaaabc", "abc"));
        assertEquals(4, findLastIndexOf("aaaaabc", "a+b"));
        assertEquals(6, findLastIndexOf("aabcaaabc", "a+b"));
        assertEquals(2, findLastIndexOf("abcde", "c"));
        assertEquals(2, findLastIndexOf("abcdef", "c"));
        assertEquals(2, findLastIndexOf("abcd", "c"));
    }

    public static int findLastIndexOf(String haystack, String needle) {
        return findLastIndexOf(0, haystack.length(), Pattern.compile(needle).matcher(haystack));
    }

    private static int findLastIndexOf(int start, int end, Matcher m) {
        if ( start > end ) {
            return -1;
        }

        int pivot = ((end-start) / 2) + start;
        if ( m.find(pivot) ) {
            //recurse on right side
            return findLastIndexOfRecurse(end, m);
        } else if (m.find(start)) {
            //recurse on left side
            return findLastIndexOfRecurse(pivot, m);
        } else {
            //not found at all between start and end
            return -1;
        }
    }

    private static int findLastIndexOfRecurse(int end, Matcher m) {
        int foundIndex = m.start();
        int recurseIndex = findLastIndexOf(foundIndex + 1, end, m);
        if ( recurseIndex == -1 ) {
            return foundIndex;
        } else {
            return recurseIndex;
        }
    }

}

Я еще не нашел контрольного теста на взлом.

2 голосов
/ 02 июля 2014

Использовать отрицательный взгляд:

String in = "num 123 num 1 num 698 num 19238 num 2134";
Pattern p = Pattern.compile("num (\\d+)(?!.*num \\d+)");
Matcher m = p.matcher(in);

if (m.find()) {
    in= m.group(1);
}

Регулярное выражение читается как «num, за которым следует один пробел и, по крайней мере, одна цифра без каких-либо (num, за которыми следуют один пробел и хотя бы одна цифра) в любой точке послеit ".

Вы можете стать еще интереснее, сочетая его с положительным взглядом:

String in = "num 123 num 1 num 698 num 19238 num 2134";
Pattern p = Pattern.compile("(?<=num )\\d+(?!.*num \\d+)");
Matcher m = p.matcher(in);

if (m.find()) {
    in = m.group();
}

То, что читается как" хотя бы одна цифра, перед которой стоит (число и один пробел), а не следует "через (num, за которым следуют один пробел и хотя бы одна цифра) в любой точке после него ".Таким образом, вам не нужно возиться с группировкой и беспокоиться о потенциальном IndexOutOfBoundsException выбросе Matcher.group(int).

2 голосов
/ 21 июня 2011

Шаблоны Java по умолчанию жадные, это должно делать следующее.

    String in = "num 123 num 1 num 698 num 19238 num 2134";
    Pattern p = Pattern.compile( ".*num ([0-9]+).*$" );
    Matcher m = p.matcher( in );

    if ( m.matches() )
    {
        System.out.println( m.group( 1 ));
    }
1 голос
/ 04 января 2013
String in = "num 123 num 1 num 698 num 19238 num 2134";  
Pattern p = Pattern.compile("num '([0-9]+) ");  
Matcher m = p.matcher(in);  
String result = "";

while (m.find())
{
     result = m.group(1);
}
0 голосов
/ 29 августа 2014

По сравнению с принятым в настоящее время ответом, он не отбрасывает вслепую элементы списка, используя префикс ".*".Вместо этого он использует "(element delimiter)*(element)", чтобы выбрать последний элемент, используя .group(2).См. Функцию magic_last в приведенном ниже коде.

Чтобы продемонстрировать преимущества этого подхода, я также включил функцию для выбора n-го элемента, который достаточно устойчив, чтобы принять список, в котором меньше nэлементы.См. Функцию magic в приведенном ниже коде.

Фильтрация текста «num» и получение только числа осталось в качестве упражнения для читателя (просто добавьте дополнительную группу вокруг шаблона цифр: ([0-9]+)и выберите группу 4 вместо группы 2).

package com.example;

import static java.lang.System.out;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Foo {

  public static void main (String [] args) {
    String element = "num [0-9]+";
    String delimiter = ", ";
    String input;
    input = "here is a num bro: num 001; hope you like it";
    magic_last(input, element, delimiter);
    magic(1, input, element, delimiter);
    magic(2, input, element, delimiter);
    magic(3, input, element, delimiter);
    input = "here are some nums bro: num 001, num 002, num 003, num 004, num 005, num 006; hope you like them";
    magic_last(input, element, delimiter);
    magic(1, input, element, delimiter);
    magic(2, input, element, delimiter);
    magic(3, input, element, delimiter);
    magic(4, input, element, delimiter);
    magic(5, input, element, delimiter);
    magic(6, input, element, delimiter);
    magic(7, input, element, delimiter);
    magic(8, input, element, delimiter);
  }

  public static void magic_last (String input, String element, String delimiter) {
    String regexp = "(" + element + delimiter + ")*(" + element + ")";
    Pattern pattern = Pattern.compile(regexp);
    Matcher matcher = pattern.matcher(input);
    if (matcher.find()) {
        out.println(matcher.group(2));
    }
  }

  public static void magic (int n, String input, String element, String delimiter) {
    String regexp = "(" + element + delimiter + "){0," + (n - 1) + "}(" + element + ")(" + delimiter + element + ")*";
    Pattern pattern = Pattern.compile(regexp);
    Matcher matcher = pattern.matcher(input);
    if (matcher.find()) {
        out.println(matcher.group(2));
    }
  }

}

Вывод:

num 001
num 001
num 001
num 001
num 006
num 001
num 002
num 003
num 004
num 005
num 006
num 006
num 006
0 голосов
/ 25 октября 2012

Этот подход выглядит более правдоподобным.

    public class LastMatchTest {
        public static void main(String[] args) throws Exception {
            String target = "num 123 num 1 num 698 num 19238 num 2134";
            Pattern regex = Pattern.compile("(?:.*?num.*?(\\d+))+");
            Matcher regexMatcher = regex.matcher(target);

            if (regexMatcher.find()) {
                System.out.println(regexMatcher.group(1));
            }
        }
    }

.*? - неохотное совпадение, поэтому оно не сожрет все. ?: заставляет группу без захвата, поэтому внутренняя группа является группой 1. Сопоставление кратных значений жадным образом приводит к тому, что оно совпадает по всей строке, пока все совпадения не будут исчерпаны, оставляя группу 1 со значением вашего последнего совпадения.

0 голосов
/ 17 июня 2012

Регулярные выражения жадные:

Matcher m=Pattern.compile(".*num '([0-9]+) ",Pattern.DOTALL).matcher("num 123 num 1 num 698 num 19238 num 2134");

даст вам Matcher за последний матч, и вы можете применить его к большинству регулярных выражений, добавив ". *". Конечно, если вы не можете использовать DOTALL, возможно, вы захотите использовать (?:\d|\D) или что-то похожее на ваш шаблон.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...