Очередной конфуз, regexp, или за что я не люблю java

в 2:45, , рубрики: bug, java, python, regexp, script, tcl, проблемы программирования, Программирование, Регулярные выражения, метки: , , , , , , ,

Нужно было мне намедни расширить Tokenizer для одного известного поисковика на джаве. Всяческие стандартные токенайзеры ну никак не хотели делать то, что нужно было, даже с включением разных токен фильтров типа WordDelimiter и ко. Кто в теме, знает какой с этим бывает геморрой, особенно если используем мультиязычные фильтры — токенайзер то один.

Так вот, на java крутить тест-кейсы довольно муторно (длинный код, вечный ескейп, долгий деплой, и т.д. и т.п.), я пытаюсь это делать как можно меньше, поэтому отрабатывал регулярки на тестах в tcl (ну люблю я этот язык).

Так вот накидал такое вот в тикле, даволен как слон все работает как хотел:

% join [regexp -inline -all {[w][w-&]+|[d-][d-*.,/]+} 
    "US-Währung, C&A, VW-Bus. 1.2.3 -23.456,78 12,345.00 12/01/2012"] " | "
US-Währung | C&A | VW-Bus | 1.2.3 | -23.456,78 | 12,345.00 | 12/01/2012

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

Короче, регулярка в порядке — лезем в джаву.
Помучился немного Проэскейпил строчку, и добавил \xc0-\xff для умляутов (ну не хочет джава понимать на w ни unicode ни utf-8). Ладно получили это:

import java.util.regex.*;
Pattern p = Pattern.compile("[\w\xc0-\xff][\w\xc0-\xff\-&]+|[\d\-][\d\-*.,/]+");
Matcher m = p.matcher("US-Währung, C&A, VW-Bus. 1.2.3 -23.456,78 12,345.00 12/01/2012");
String s = "";
while (m.find()) { // find next match
   s += m.group() + " | ";
}
print(s);

US-Währung | C&A | VW-Bus | 1.2.3 | -23.456,78 | 12 | 345 | 00 | 12 | 01 | 2012 | 

И тут на тебе — вижу, что некоторые числа расползлись по разным токенам. Хотя после проверки регулярного выражения такое однозначно не должно было бы быть. Баг или фича. Полез в документацию, нет — все верно «greedy match» по умолчанию, но в результате-то имеем «reluctant match».
Кстати, хорошее разъяснение greedy от Wott нашел для тех кто не в курсе здесь.
Если коротко то жадный match выберет самую длинную строку из всех возможных соответствующих выражению. Вообщем как в примере на тикле.

Для проверки убрал к чертям цифры из первого выражения до OR "|":

Pattern p = Pattern.compile("[a-zA-Z\xc0-\xff][a-zA-Z\xc0-\xff\-&]+|[\d\-][\d\-*.,/]+");
...
US-Währung | C&A | VW-Bus | 1.2.3 | -23.456,78 | 12,345.00 | 12/01/2012 | 

Баг — однозначно баг. Но почему число с минусом вырывалось все равно нормально? И тут меня осенило: что если OR работает как в булевых выражениях в компиляторе — т.е. если первое условие выполняется, то второе не проверяется вовсе?(всякие прагмы побоку не берем во внимание). Очень глупая фича для регулярок, однако все-таки вдруг.

Вернул цифры обратно через w, поменял местами выражения и…

Pattern p = Pattern.compile("[\d\-][\d\-*.,/]+|[\w\xc0-\xff][\w\xc0-\xff\-&]+");
...
US-Währung | C&A | VW-Bus | 1.2.3 | -23.456,78 | 12,345.00 | 12/01/2012 | 

Оно работает — блин,… пушной северный зверек.

Полез проверять в python и… то же самое:

>>> re.findall(re.compile(ur'[wxc0-xff][wxc0-xff-&]+|[d-][d-*.,/]+', re.U),  
    u"US-Währung, C&A, VW-Bus. 1.2.3 -23.456,78 12,345.00 12/01/2012")
[u'US-Währung', u'C&A', u'VW-Bus', u'1.2.3', u'-23.456,78', u'12', u'345', u'00', u'12', u'01', u'2012']
>>> re.findall(re.compile(r'[d-][d-*.,/]+|[wxc0-xff][wxc0-xff-&]+', re.U), 
    u"US-Währung, C&A, VW-Bus. 1.2.3 -23.456,78 12,345.00 12/01/2012")
[u'US-Währung', u'C&A', u'VW-Bus', u'1.2.3', u'-23.456,78', u'12,345.00', u'12/01/2012']

Ну да ладно — на python я не в обиде — первый раз слажал на моей памяти. Хотя тоже неприятно, однако.

Но java-то. Просто постоянно что-нибудь, да выкинет. Ни одного разу, как я садился делать на джаве что-либо серьезное, не проходило без плясок с бубном под барабаны. То одно, то другое. Хотя для честности нужно сказать, что большее количество лагов приходились все-таки на какие-нибудь библиотеки. Но это-то java.util — базис одно слово.
И ведь никогда уже наверно не пофиксят — backwards compatibility forever!

Я уже давно перестал холиварить на тему «чья корова ближе к телу», т.е. мне в принципе совершенно фиолетого на каком языке писать. Конечно есть предпочтения, бывают разные ограничения, архитектурного например или клиентского разлива (типа хочу php), есть конечно и наработаная база. Но по большому счету, все-таки все равно.
Только джаву я все чаще стараюсь обходить стороной. У меня остался последний большой проект (технология) SOLR/Lucene под java, сяду и перепишу нафиг под C++ или C#. Осталось только время найти :).

Кстати, это все случилось на винде…
Пошел проверять под debian.

Автор: sebres

Поделиться

* - обязательные к заполнению поля