При условии Unicode и нечувствительности к регистру, шаблон ".." должен соответствовать "FfIsS"?
Звучит как шутка, но я могу это доказать.
Предположения:
- Точка соответствует любому отдельному символу.
- Совпадения с учетом регистра символов
s
если и только если это соответствуетs.toUpperCase()
,
Все следующее довольно логично и верно в Java:
"ffi".matches(".")
ЛАТИНСКАЯ МАЛЕНЬКАЯ ЛИГАТУРА FFI (U+FB03) - символ, поэтому он должен соответствовать"ß".matches(".")
LATIN SMALL LETTER SHARP S (U+00DF) - символ, поэтому он должен соответствовать"ffi".toUpperCase().equals("FFI")
по стандарту Unicode (нет заглавной лиги FFI)"ß".toUpperCase().equals("SS")
по стандарту Unicode (есть заглавная S, но она не привыкает)"FfI".toUpperCase().equals("FFI")
очевидно"sS".toUpperCase.equals("SS")
очевидно
Таким образом, предполагая, что первая точка в регулярном выражении означает ffi
а второй для ß
, регулярное выражение должно соответствовать "FFISS" и из-за нечувствительности к регистру также "FfIsS".
Я действительно надеюсь, что что-то не так, в противном случае регулярные выражения станут довольно непригодными.
Вопросы:
- Что не так с моим "доказательством"?
- Что означает "без учета регистра", если мое второе предположение не выполняется?
2 ответа
На случай складывания
Ответ - нет, точка не будет совпадать ss
без учета регистра, хотя причины немного эзотерические.
Однако некоторые из тех, кто знает о таких вещах, часто поднимали вашу головоломку, потому что они тоже считают, что это ведет к противоречиям.
В Unicode есть две формы отображения случаев. Существует простое отображение случая, в котором одна кодовая точка когда-либо отображается только на одну другую кодовую точку. Так что если length(s) == 1
тогда вам гарантировано, что length(fc(s)) == 1
также, где fc
является картой в формате Unicode Но это также относится к uc
, tc
, а также lc
отображения случая.
Проблема в том, что при анализе определенных видов реального текста вы не получите столь же хороших результатов, как тогда, когда вы даете такие гарантии точной длины 1:1.
На самом деле, их довольно много. Числа показывают, сколько отдельных кодовых точек BMP отображается на указанные длины под четырьмя картами случаев:
length lc == 2 1
length lc == 3 0
length fc == 2 88
length fc == 3 16
length uc == 2 86
length uc == 3 16
length tc == 2 32
length tc == 3 16
В полном, а не в простом, который использует регулярное выражение Java, вы можете получить такие вещи, как tschüß
а также TSCHÜSS
чтобы соответствовать, даже если они имеют неодинаковую длину. Perl и Ruby используют полное отображение регистра при выполнении сравнения без учета регистра. Это приводит к странным парадоксам в отрицательных классах персонажей, если вы не будете осторожны.
Но вот в чем проблема: сопоставление без учета регистра не выполняет транзитивную операцию. Другими словами, если .
Матчи ß
и при сопоставлении без учета регистра, ß
Матчи SS
, это не значит, что через транзитивность .
Матчи SS
без учета регистра. Просто так не получается, хотя умнее людей, чем я, глубоко задумался над этим вопросом.
Однако оба эти кодовых пункта:
- U + 00DF ß ЛАТИНСКАЯ МАЛЕНЬКАЯ ПИСЬМА SHARP S
- U + 1E9E L ПИСЬМО ЛАТИНСКОГО КАПИТАЛА SHARP S
безусловно, регистронезависимо совпадают не только друг с другом, но и SS
, Ss
, sS
, а также ss
при полном картографировании. Они просто не делают это при простом картографировании.
Unicode дает некоторые гарантии по этому поводу. Во-первых, если length(s) == n
, тот length(fn(s)) <= 3*n
где fn
является любой из четырех карт случая: lc
, fc
, uc
, а также tc
,
На нормализацию
Если вы думаете, что это плохо, то на самом деле становится намного хуже, когда вы рассматриваете формы нормализации. Здесь гарантия 5×, а не 3×. Так length(NFx(s)) <= 5 * length(s)
, который, как вы видите, становится дорогим.
Вот эквивалентная таблица, показывающая, сколько точек кода расширяется до нескольких единиц в каждой из четырех форм нормализации:
length NFC == 2 70
length NFC == 3 2
length NFC == 4 0
length NFC == 5 0
length NFKC == 2 686
length NFKC == 3 404
length NFKC == 4 53
length NFKC == 5 15
length NFD == 2 762
length NFD == 3 220
length NFD == 4 36
length NFD == 5 0
length NFKD == 2 1345
length NFKD == 3 642
length NFKD == 4 109
length NFKD == 5 16
Разве это не замечательно? Некоторое время Unicode хотел попытаться встроить каноническую эквивалентность в сопоставление с образцом. Они знали, что это дорого по указанным выше причинам, но им потребовалось некоторое время, чтобы понять, что это было принципиально невозможно из-за необходимого канонического переупорядочения сочетания символов в одном графемном блоке.
По этой и многим другим причинам текущая рекомендация, если вы хотите сравнить вещи "без учета регистра" или "без учета нормализации", состоит в том, чтобы самостоятельно выполнить ее через преобразование с обеих сторон, а затем сравнить результаты.
Например, учитывая подходящий ==
оператор эквивалентности код-точка-код-точка
fc(a) == fc(b)
и аналогично для =~
оператор сопоставления с образцом (который работает традиционным способом, конечно же, не как сломанный Java match
метод, который неуместно привязывает вещи):
fc(a) =~ fc(b)
Проблема заключается в том, что вы больше не можете включать или выключать нечувствительность к регистру в определенных частях шаблона, таких как
/aaa(?i:xxx)bbb/
и иметь только xxx
часть будет делаться без учета регистра.
Полный корпус сложен, но его можно (в большинстве случаев) сделать, как доказали Perl и Ruby. Но это также довольно неинтуитивно (читай: удивительно) в местах, которые вы должны понимать. Вы должны делать специальные вещи с классами символов в скобках, особенно с их отрицаниями, или это приводит к бессмыслице.
Соответствие локали
Наконец, чтобы сделать вещи действительно сложными, в реальном мире вы должны сделать больше, чем одно или оба из отображения случаев и нормализации. В некоторых национальных регионах все сложнее. Например, в немецкой телефонной книге гласный с умлаутом считается точно так же, как и тот же базовый гласный, за которым следует буква e. Итак, что-то вроде müß
ожидается, чтобы соответствовать MUESS
регистронезависимо.
Чтобы сделать все это правильно, вам действительно нужно связать не только таблицы полного отображения и нормализации, сам DUCET, таблицу элементов сравнения Unicode по умолчанию и даже данные CLDR (см. Библиографию):
#!/usr/bin/perl
use utf8;
use open qw(:utf8 :std);
use Unicode::Collate::Locale;
my $Collator = Unicode::Collate::Locale->new(
locale => "de__phonebook",
level => 1,
normalization => undef,
);
my $full = "Ich müß Perl studieren.";
my $sub = "MUESS";
if (my ($pos,$len) = $Collator->index($full, $sub)) {
my $match = substr($full, $pos, $len);
print "Found match of literal ‹$sub› at position $pos in ‹$full› as ‹$match›\n";
}
Если вы запустите это, вы обнаружите, что это действительно работает:
Найдено совпадение буквального ‹MUESS› в положении 4 в ‹Ich müß Perl studieren.› As ‹müß›
Избранная библиография
Большинство из этих примеров были взяты из 4- го издания Programming Perl с любезного разрешения его автора.:) Я довольно много пишу о таких вопросах, связанных с Unicode, вещи, которые не являются специфичными для Perl, но являются общими для Unicode в целом.
Программа unichars (1), которая позволяет мне собирать такую статистику:
$ unichars 'length fc == 2' | wc -l
88
$ unichars 'length NFKD == 4' | wc -l
109
$ unichars '/ss/i'
U+00DF ß LATIN SMALL LETTER SHARP S
U+1E9E ẞ LATIN CAPITAL LETTER SHARP S
Является частью набора модулей Unicode:: Tussle CPAN, который Брайан Фой любезно поддержал.
Для дальнейшего чтения
Смотрите также:
Как отметил в своем комментарии Мааартинус, Java обеспечивает (по крайней мере, в теории) поддержку Юникода для нечувствительного к регистру соответствия reg-exp. Формулировка в документации по API Java заключается в том, что сопоставление выполняется "в соответствии со стандартом Unicode". Проблема, однако, в том, что стандарт Unicode определяет разные уровни поддержки для преобразования регистра и сопоставления без учета регистра, а в документации API не указано, какой уровень поддерживается языком Java.
Хотя это и не задокументировано, по крайней мере в Oracle VM VM, реализация reg-exp ограничена так называемым простым нечувствительным к регистру соответствием. Ограничивающие факторы, относящиеся к данным вашего примера, заключаются в том, что алгоритм сопоставления работает только так, как ожидается, если сворачивание (преобразование) в регистр приводит к одинаковому количеству символов, а наборы (например, ".") Ограничены, чтобы соответствовать точно одному символу во входных данных. строка. Первое ограничение даже приводит к тому, что "ß" не соответствует "SS", как вы, возможно, и ожидали.
Чтобы получить поддержку для полного нечувствительного к регистру совпадения между строковыми литералами, вы можете использовать реализацию reg-exp в библиотеке ICU4J, так что по крайней мере "ß" и "SS" совпадают. AFAIK, однако, не существует реализаций reg-exp для Java с полной поддержкой групп, наборов и подстановочных знаков.