Java - проблема регулярных выражений - PullRequest
3 голосов
/ 15 апреля 2010

Я хочу удалить символ ) в конце строки через регулярное выражение.

Eg. Если строка - Великобритания (Великобритания), то я хочу заменить последний символ ).

Примечание:

1). Регулярное выражение должно удалять только последний символ ), независимо от того, сколько символов ) присутствует в строке.

Ответы [ 5 ]

9 голосов
/ 15 апреля 2010

Пожалуйста, , не используйте регулярное выражение для этой простой задачи.

// If the last ) might not be the last character of the String
String s = "Your String with) multiple).";
StringBuilder sb = new StringBuilder(s);
sb.deleteCharAt(s.lastIndexOf(')'));
s = sb.toString(); // s = "Your String with) multiple."

// If the last ) will always be the last character of the String
s = "Your String with))";
if (s.endsWith(")")) 
    s = s.substring(0, s.length() - 1);
// s = "Your String with)"
3 голосов
/ 15 апреля 2010

Если удаляется только ) в конце строки, то это работает:

str.replaceFirst("\\)$", "");

Это в точности соответствует тому, что написано: литерал ) (экранированный, потому что это также метасимвол регулярного выражения), за которым следует $, привязка границы конца строки, и замена его пустой строкой, эффективно удаляя любые окончание ).

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


Если вы обычно хотите удалить последнее вхождение ), которое может отсутствовать в конце строки, вы можете использовать жадное совпадение .*:

str.replaceFirst("(.*)\\)", "$1");

Здесь мы имеем жадное совпадение .*, которое фиксируется в \1. Если бы весь шаблон когда-либо совпадал, \1 был бы настолько длинным, насколько это возможно, что означает, что литерал ), следующий за ним, должен был быть последним вхождением (потому что, если в его случае есть другое вхождение верно, \1 мог бы вместо этого захватить более длинную строку, что противоречит).


Производительность

Соответствие первому регулярному выражению должно быть оптимизируемым для операции O(1), благодаря привязке конца строки $. Фактическая замена будет O(N), потому что новая строка должна быть скопирована в новый буфер, если есть совпадение. Если совпадения нет, то должно быть оптимизируемым для возврата исходной строки, и, следовательно, было бы O(1) в целом. Это настолько оптимально, насколько это возможно.

Второму регулярному выражению требуется O(N), чтобы соответствовать из-за повторения. Это не хуже, чем линейный поиск последних ) с использованием lastIndexOf, который также O(N).

Если вы делаете это много, то вы должны знать стандартную скомпилированную Pattern эквивалентность replaceFirst. От API :

Вызов этого метода в форме

str.replaceFirst(regex, repl)

дает точно такой же результат, как выражение

Pattern.compile(regex).matcher(str).replaceFirst(repl)

читаемость

"Вызов метода replaceFirst, который был взломан для фактической замены последнего, просто сбивает с толку."

Здесь следует указать, что на самом деле вы можете использовать replaceAll с этими точными шаблонами, и решение все равно будет работать! На самом деле вам просто нужно заменить регулярное выражение, и либо из replaceAll или replaceFirst это действительно не имеет значения, шаблон действительно такой простой !

needle$ для сопоставления в конце строки и жадный (.*)needle для сопоставления с последним вхождением - это основные идиомы, которые очень удобочитаемы и понятны для тех, кто имеет базовое понимание регулярных выражений. Ни один из них на самом деле не может считаться «хаком».

Использование метода под названием replaceFirst для замены последнего вхождения чего-либо может сначала показаться вводящим в заблуждение, но это недальновидно: это первое совпадение шаблона , которое заменены; соответствие этому шаблону может быть чем угодно, будь то шестым "Sense" или последним "Mohican"!

В качестве аналогии, давайте возьмем другой простой пример манипуляции со строками: удалите все подстроки "spam" из строки. Я бы сказал, что наиболее читаемым решением является использование replace

str.replace("spam", "");

"Но подождите! Имя replace вводит в заблуждение! Вы не заменяете его чем-то другим! Вам следует вызвать метод с именем delete или что-то еще!"

Это глупо, конечно! Вы действительно заменяете это чем-то другим - пустой строкой! Его эффект - удаление, но операция все еще строковая replace -ment!

Точно так же, как replaceFirst в моем решении: вы можете заменить последнее вхождение чего-либо, но это все же первое совпадение общего шаблона!

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

public static String removeLastCloseParenthesis(String str) {
   return str.replaceFirst("(.*)\\)", "$1");
}

И вы всегда можете просто назвать вещь . И вы всегда можете поставить комментарии как / при необходимости . Это всего лишь общие методы читабельности кода, и поэтому они применимы к регулярным выражениям так же, как и ко всему остальному.

2 голосов
/ 15 апреля 2010

Если вы хотите использовать регулярное выражение (несмотря на то, что это выполнимо без регулярного выражения)

String s = /* ... your string here ... */
String parenReplacement = "!!!" // whatever the replacement is
Pattern p = Pattern.compile("^(.*)\\)([^\\)]*)$");
Matcher m = p.matcher(s);
if (m.find())
{
   s = m.group(1)+parenReplacement+m.group(2);
}
1 голос
/ 15 апреля 2010

Зачем вы используете для этого регулярное выражение? Просто используйте String.charAt (...) и подстроку (...)!

0 голосов
/ 15 апреля 2010

Вам не нужно регулярное выражение для этого. Класс String имеет метод lastIndexOf(), который можно использовать для поиска индекса последнего) в String. Смотри здесь .

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