Регулярное выражение в Java работает не так, как мне нужно
public class Test {
public static Map<Integer, String> map = new HashMap<Integer, String>();
static {
map.put(1, "один");
map.put(12, "двенадцать");
}
public static void main(String[] args) {
String s = "Это стоит 1 бакс, а вот это - 12 .";
for (Integer integer : map.keySet()) {
Pattern r = Pattern.compile("\\b" + integer + "\\b");
Matcher m = r.matcher(s);
if (m.find()){
s = s.replace(String.valueOf(integer), map.get(integer));
}
}
System.out.println(s);
}
}
На https://regex101.com/ если я напишу регулярное выражение \b1\b - найдет только первую единицу перед словом "бакс", в числе 12 - её не найдет. Но когда я запускаю этот код, то строка меняется на "Это стоит один бакс, а вот это - один2 .", а не на нужное мне "Это стоит один бакс, а вот это - двенадцать ." Почему ? Это особенности обработки регулярных выражений на Java ? Как записать регулярное выражение, чтобы получить нужный мне результат ?
Ответы (2 шт):
Это не особенность Java (вот пруф: https://www.regexplanet.com/share/index.html?share=yyyyd274wyr).
Проблема в логике Вашей программы. Вы используете s = s.replace(String.valueOf(integer), map.get(integer)); для замены числа. Эта инструкция срабатывает или не срабатывает для строки в целом, не принимая во внимание никаких assertions в предшествующем регулярном выражении Pattern.compile("(?<=\\D)" + integer + "(?=\\D)");.
Вот этот код с replaceAll сделает всё:
import java.util.*;
import java.util.regex.*;
public class Test {
public static Map<Integer, String> map = new HashMap<Integer, String>();
static {
map.put(1, "один");
map.put(12, "двенадцать");
}
public static void main(String[] args) {
String s = "Это стоит 1 бакс, а вот это - 12.";
for (Integer integer : map.keySet()) {
s = s.replaceAll("\\b" + integer + "\\b", map.get(integer));
}
System.out.println(s);
}
}
В вашей программе числа, найденные с помощью регулярного выражения, заменяются уже другим вызовом метода String#replace, то есть происходит замена каждого найденного числа независимо от контекста, целое это число или нет.
Следует находить и заменять совпадения непосредственно при поиске, тогда не будет возникать подобных проблем.
import java.util.*;
import java.util.stream.*;
import java.util.regex.*;
import static java.util.Map.entry;
class Ideone
{
public static Map<Integer, String> map = Map.ofEntries(
entry(1, "один"),
entry(12, "двенадцать")
);
public static void main (String[] args) throws java.lang.Exception
{
String s = "Это стоит 1 бакс, а вот это - 12 .";
List<Integer> lst = new ArrayList<>(map.keySet()); // Ключи как список целочисленных значений
String rx = "\\b(" + String.join("|", lst.stream().map(Object::toString).collect(Collectors.toList())) + ")\\b"; // Создаем регулярное выражение, тут оно выглядит как \b(1|12)\b
Pattern p1 = Pattern.compile(rx); // Компилируем объект шаблона
Matcher m = p1.matcher(s); // Объявляем объект Matcher и передаем в него строку
String result = m.replaceAll(x -> map.get(Integer.parseInt(x.group())) ); // Заменяем при нахождении сразу
System.out.println( result ); // Выводим результат на консоль
}
}
Результат: Это стоит один бакс, а вот это - двенадцать .
import java.util.*;
import java.util.regex.*;
class Wandbox
{
public static Map<Integer, String> map = new HashMap<Integer, String>();
static {
map.put(1, "один");
map.put(12, "двенадцать");
}
public static void main(String[] args) {
String s = "Это стоит 1 бакс, а вот это - 12.";
List<String> lst = new ArrayList<>();
for (Integer i:map.keySet()) { lst.add(i.toString()); }
String rx = "\\b(" + String.join("|", lst) + ")\\b";
StringBuffer result = new StringBuffer();
Matcher m = Pattern.compile(rx).matcher(s);
while (m.find()) {
String val = map.get(Integer.parseInt(m.group()));
m.appendReplacement(result, val);
}
m.appendTail(result);
System.out.println(result);
}
}