java - online - pattern matcher
Suponiendo que Unicode y la insensibilidad a las mayúsculas y minúsculas, ¿debería el patrón ".." coincidir con "FfIsS"? (2)
Suena como una broma, pero puedo demostrarlo.
Suposiciones
- Punto coincide con cualquier carácter individual.
- Un patrón que no distingue entre mayúsculas y minúsculas coincide con
s
si y solo si coincide cons.toUpperCase()
.
Todo lo siguiente es bastante lógico y se mantiene en Java:
-
"ffi".matches(".")
LATAT LATURA PEQUEÑA FFI (U + FB03) es un carácter, por lo que debe coincidir -
"ß".matches(".")
LATIN SMALL LETTER SHARP S (U + 00DF) es un carácter, por lo que debe coincidir -
"ffi".toUpperCase().equals("FFI")
según el estándar Unicode (no hay FFI de ligadura de capital) -
"ß".toUpperCase().equals("SS")
según el estándar Unicode (hay una S mayúscula, pero no se usa) -
"FfI".toUpperCase().equals("FFI")
obviamente -
"sS".toUpperCase.equals("SS")
obviamente
Entonces, asumiendo que el primer punto en la expresión regular significa stands y el segundo para ß
, la expresión regular debe coincidir con "FFISS" y debido a la insensibilidad de las mayúsculas y minúsculas también "FfIsS".
Realmente espero que haya algo mal, de lo contrario las expresiones regulares se volverían bastante inutilizables.
Las preguntas:
- ¿Qué pasa con mi "prueba"?
- ¿Qué significa exactamente "sin distinción de mayúsculas y minúsculas" si mi segunda suposición no se cumple?
En caja plegable
La respuesta es no, el punto no coincidirá con el caso de ss
insensiblemente, aunque las razones son ligeramente esotéricas.
Sin embargo, su rompecabezas a menudo ha sido planteado por algunos de los que más saben sobre estas cosas, porque ellos también sienten que conduce a contradicciones.
Hay dos formas de mapeo de casos en Unicode. Existe una asignación de casos simple en la que un punto de código solo se asigna a exactamente otro punto de código. Entonces, si la length(s) == 1
, entonces se le garantiza esa length(fc(s)) == 1
también, donde fc
es el mapa de casos de Unicode. Pero también se aplica a los mapeos de casos uc
, tc
y lc
.
El problema con eso es que no obtiene los mejores resultados al analizar ciertos tipos de texto del mundo real, por lo que hace ese tipo de garantías de longitud exacta de 1: 1.
De hecho, hay bastantes de estos. Los números indican cuántos puntos de código BMP individuales se asignan a las longitudes especificadas en los cuatro mapas de casos:
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
En la carcasa completa, no en la carcasa simple que utiliza la expresión regular de Java, puede hacer que tschüß
cosas como tschüß
y TSCHÜSS
, aunque sean de longitudes desiguales. Perl y Ruby usan el mapeo completo de casos cuando hacen comparaciones entre mayúsculas y minúsculas. Esto lleva a extrañas paradojas en las clases de caracteres negados si no tienes cuidado.
Pero aquí está el problema: la coincidencia entre mayúsculas y minúsculas no realiza una operación transitiva. En otras palabras, si .
coincide con ß
y en el caso de concordancia insensible, ß
coincide con SS
, eso no significa que a través de transitividad .
coincide con SS
caso SS
insensiblemente. Simplemente no funciona de esa manera, aunque las personas más inteligentes que yo han pensado profundamente sobre el tema.
Sin embargo, estos dos puntos de código:
- U + 00DF ß SHARP DE LETRA PEQUEÑA LATINA S
- U + 1E9E SH LATINO DE CARTAS DE CAPITAL LATINO SHARP S
indudablemente, no solo se deben hacer coincidir entre mayúsculas y minúsculas, sino también SS
, Ss
, sS
y ss
en el mapeo completo de casos. Simplemente no lo hacen bajo un simple mapeo de casos.
Unicode hace algunas garantías sobre esto. Uno es que si length(s) == n
, esa length(fn(s)) <= 3*n
donde fn
es cualquiera de los cuatro mapas de caso: lc
, fc
, uc
y tc
.
En la normalizacion
Si piensa que eso es malo, en realidad empeora mucho si considera los formularios de normalización. Aquí la garantía es 5 × no 3 ×. Por lo tanto, la length(NFx(s)) <= 5 * length(s)
, que, como ves, se está volviendo costosa.
Aquí está la tabla equivalente que muestra cuántos puntos de código se expanden a más de uno en cada uno de los cuatro formularios de normalización:
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
¿No es eso extraordinario? Por un tiempo, Unicode quería tratar de construir una equivalencia canónica en su coincidencia de patrones. Sabían que era caro por las razones que acabo de mencionar, pero tardaron un tiempo en darse cuenta de que era fundamentalmente imposible debido a la necesaria ordenación canónica de combinar caracteres dentro de una unidad de grafema.
Por esta razón, y muchas otras, la recomendación actual si quiere comparar las cosas "sin distinción de mayúsculas y minúsculas" o "insensiblemente a la normalización" es ejecutarlo a través de la transformación en ambos lados y luego comparar los resultados.
Por ejemplo, dado un operador de equivalencia de ==
punto de código por punto de código adecuado
fc(a) == fc(b)
y de manera similar para un operador de coincidencia de patrones =~
(que funciona de la manera tradicional, por supuesto, no como el método de match
rota de Java que ancla las cosas de manera inapropiada):
fc(a) =~ fc(b)
El problema con esto es que ya no se puede activar o desactivar la insensibilidad de mayúsculas y minúsculas en partes específicas de un patrón, como
/aaa(?i:xxx)bbb/
y hacer que solo la parte xxx
se haga de forma insensible.
La cubierta completa es difícil, pero puede (en la mayoría de las circunstancias) hacerse, como lo han demostrado Perl y Ruby. Pero también es bastante no intuitivo (léase: sorprendente) en lugares que debería entender. Tienes que hacer cosas especiales con clases de caracteres entre corchetes, especialmente con sus negaciones, o eso lleva a tonterías.
Coincidencia local
Finalmente, para hacer las cosas realmente complicadas, en el mundo real, tiene que hacer más que cualquiera o ambos de la asignación de casos y la normalización. En ciertos lugares nacionales, las cosas son más complicadas. Por ejemplo, en una guía telefónica alemana, y la vocal con diéresis cuenta exactamente lo mismo que la misma vocal de base seguida de la letra e . Entonces, se müß
que algo así como müß
emparejara a MUESS
de forma insensible al caso.
Para hacer todo esto bien, realmente necesita vincular no solo la asignación de casos completa y las tablas de normalización, el propio DUCET, la tabla de elementos de intercalación por defecto de Unicode e incluso los datos de CLDR (consulte la bibliografía):
#!/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";
}
Si ejecuta eso, descubrirá que sí funciona:
Coincidencia encontrada de literal ‹MUESS› en la posición 4 en ‹Ich müß Perl studieren.› As ‹müß›
Bibliografía seleccionada
La mayoría de estos ejemplos se tomaron de la 4ª edición de Programming Perl con el amable permiso de su autor. :) Escribo bastante sobre tales asuntos relacionados con Unicode, cosas que no son específicas de Perl, sino que son generales para Unicode en general.
El programa unichars (1) que me permite recopilar estadísticas como las siguientes:
$ 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
Es parte del conjunto de módulos CPAN Unicode::Tussle que Brian Foy ha tenido la amabilidad de mantener para mí.
Para leer más
Ver también:
Como lo señaló maaartinus en su comentario, Java proporciona (al menos en teoría) el soporte de Unicode para el emparejamiento reg-exp insensible a mayúsculas y minúsculas. La redacción de la documentación de la API de Java es que la coincidencia se realiza "de una manera coherente con el estándar de Unicode". Sin embargo, el problema es que el estándar Unicode define diferentes niveles de compatibilidad para la conversión de casos y la coincidencia entre mayúsculas y minúsculas, y la documentación de la API no especifica qué nivel admite el lenguaje Java.
Aunque no está documentado, al menos en Java VM de Oracle, la implementación de reg-exp se limita a la llamada coincidencia simple que no distingue entre mayúsculas y minúsculas. Los factores limitantes relevantes para sus datos de ejemplo son que el algoritmo de coincidencia solo funciona como se esperaba si el plegado (conversión) del caso da como resultado el mismo número de caracteres y que los conjuntos (por ejemplo, ".") Están limitados para coincidir exactamente con un carácter en la entrada cuerda. La primera limitación conduce incluso a que "ß" no coincida con "SS", como también puede haber esperado.
Para obtener soporte para la coincidencia completa entre mayúsculas y minúsculas entre los literales de cadena, puede usar la implementación reg-exp en la biblioteca ICU4J , de modo que al menos "ß" y "SS" coincidan. AFAIK, sin embargo, no hay implementaciones reg-exp para Java con soporte completo para grupos, conjuntos y comodines.