• No se han encontrado resultados

Divida en fichas una cadena empleando la API de expresiones regulares

In document Java soluciones de programación (página 56-61)

Componentes clave

Clases Métodos

java.util.regex.Pattern Pattern.compile(String expReg) Matcher matcher(CharSequence cad) java.util.regex.Matcher boolean find( )

String group( )

subrayado y es seguido por otras letras, dígitos o líneas de subrayado. Un comentario coincidirá con un patrón que inicia con // y termina con el fi nal de línea, o que empieza con /* y termina con */. Un operador coincidirá con un patrón de operador, que puede defi nirse para que incluya operadores de un solo carácter (como +) y de varios caracteres (como +=). La ventaja de esta técnica es que no es necesario que las fi chas se presenten en algún orden predefi nido ni estén separadas por un conjunto fi jo de delimitadores. En cambio, las fi chas se identifi can con el patrón con el que coinciden. Este es el método que se usará en esta solución. Como verá, es fl exible y muy adaptable.

La división en fi chas es una tarea tan importante que Java le proporciona amplio soporte integrado. Por ejemplo, proporciona tres clases especialmente diseñadas para este fi n:

StreamTokenizer, Scanner y la obsoleta StringTokenizer. Más aún (como ya se mencionó), la clase String contiene el método split( ), que también puede usarse para división en fi chas en ciertas situaciones simples. Aunque estas clases son útiles en sí mismas, son más útiles para la división en fi chas de una cadena que está defi nida a partir de delimitadores.

Para dividir en fi chas con base en patrones, por lo general encontrará que la API de expresiones regulares de Java es una mejor opción. Éste es el método empleado por la solución de esta sección. Al usar la API de expresiones regulares, obtendrá control directo y detallado de los procesos de división en fi chas. Además, la implementación de un divisor mediante el empleo de las clases Pattern y Matcher proporciona una solución elegante que es clara y fácil de comprender.

Paso a paso

Para dividir en fi chas una cadena basada en patrones mediante el uso de la API de expresiones regulares, se requieren los siguientes pasos:

1. Cree un conjunto regular de expresiones que defi na los patrones que utilizará para la búsqueda. Cada tipo de fi cha que quiera obtener debe representarse con un patrón. Para permitir que el motor de expresiones regulares recorra progresivamente la cadena, empiece cada patrón con el especifi cador de límite \G. Esto requiere que la siguiente coincidencia empiece en el punto en que terminó la anterior.

2. Compile los patrones en los objetos de Pattern empleando Pattern.compile( ). 3. Cree un Matcher que contenga la cadena que habrá de dividirse en fi chas. 4. Obtenga la siguiente fi cha al adaptar el siguiente algoritmo:

mientras(patrones fi jos a probarse) {

Especifi que el patrón que se buscará al llamar a usePattern( ) en el Matcher. Busque el patrón al llamar a fi nd( ) en el Matcher

Si se encuentra una coincidencia, se obtiene una fi cha De otra manera, se prueba el siguiente patrón. }

A menudo, las patrones deben probarse en un orden específi co. Por tanto, cuando implemente este algoritmo, debe probar cada patrón en el orden correcto.

5. Una vez que se haya encontrado una fi cha, obténgala al llamar a group( ) en el Matcher. 6. Repita los pasos 4 y 5 hasta que se alcance el fi nal de la cadena.

Análisis

El procedimiento básico con el fi n de crear un Pattern y un Matcher y para usar fi nd( ) y group( ) a fi n de buscar una cadena se describió en la solución anterior (Compare y extraiga subcadenas empleando la API de expresiones regulares), y ese análisis no se repetirá aquí. En cambio, nos concentraremos en los dos elementos clave que permiten que una cadena se divida en fi chas. El primero es la defi nición del conjunto de patrones que describe a las fi chas. El segundo es usePattern( ), que cambia el patrón que se busca.

La clave para usar la API de expresiones regulares con el fi n de dividir en fi chas una cadena es el comparador de límite \G. Al empezar el patrón con éste, cada coincidencia deben empezar precisamente donde se dejó la anterior. (En la primera coincidencia, \G coincidirá con el principio de la cadena). Empleando este mecanismo, puede llamarse varias veces al método fi nd( ) en un Matcher. Cada coincidencia posterior empezará donde terminó la anterior. Esto permite que el motor de expresiones regulares recorra la cadena, sin que omita alguna fi cha en el proceso.

He aquí algunos ejemplos de expresiones regulares que coinciden con palabras, signos de puntuación, espacios en blanco y números.

Coincidencias Patrón

Palabras \G\p{Alpha}+

Signos de puntuación \G\p{Punct} Espacio en blanco \G\s+

Números \G\d+\.?\d*

En cada caso, la fi cha que habrá de buscarse debe empezar inmediatamente después de la coincidencia anterior. Por ejemplo, dada esta cadena

Salta una, no 2, veces

Primero, "Salta" coincide con el patrón de palabra. El siguiente patrón que se encontrará es el espacio en blanco, porque es el único patrón que puede seguir a la coincidencia anterior. A continuación, "una", es encontrada por el patrón de palabra, una vez más porque es el único patrón que puede seguir a la coincidencia anterior. El patrón de signo de puntuación coincidirá después con la coma, el patrón de palabra coincidirá con "no", el patrón de número con "2", etc. Un punto clave es que debido a que cada patrón debe empezar al fi nal del anterior, no se omitirá ninguna fi cha cuando el motor de expresiones regulares trate de encontrar una coincidencia. Por ejemplo, después de que se ha encontrado "Salta", fallará un intento de encontrar un número porque "2" no se encuentra inmediatamente después de esta coincidencia.

Con el fi n de habilitar un Matcher para que use diferentes patrones, usará el método usePattern( ). Este método cambia el patrón sin restablecer todo el Matcher. Aquí se muestra:

Matcher usePattern(Pattern expReg)

El patrón que habrá de usarse es especifi cado por expReg.

Cuando se trata de obtener la siguiente fi cha, el orden en que se prueban los patrones es importante. Por ejemplo, considere estos dos patrones:

\G[<>=!]

El primer patrón coincide con los operadores de un solo carácter <, >, = y !; el segundo con <=, >=, == y !=, que son los operadores de dos caracteres. Para dividir correctamente en fi chas una cadena que contiene ambos tipos de operadores, debe primero buscar los operadores de dos caracteres y luego los de uno. Si invierte este orden, cuando se encuentre <= se recuperarán < y = como dos fi chas individuales, en lugar de una sola.

Ejemplo

En el siguiente programa se pone en práctica el análisis anterior. Divide en fi chas una cadena en sus componentes textuales: palabras, números o signos de puntuación. Aunque se trata de un ejemplo simple, ilustra las técnicas básicas empleadas para dividir en fi chas cualquier tipo de entrada. // Un divisor simple para texto.

import java.util.regex.*; class DivisorSimpleDeTexto {

// Crea patrones que coinciden con texto simple. static Pattern fin = Pattern.compile("\\G\\z"); static Pattern palabra = Pattern.compile("\\G\\w+"); static Pattern punt = Pattern.compile("\\G\\p{Punct}"); static Pattern espacio = Pattern.compile("\\G\\s");

static Pattern numero = Pattern.compile("\\G\\d+\\.?\\d*"); // Este método devuelve la siguiente ficha recuperada del // Matcher pasado a mat.

static String obtenerFichaTexto(Matcher mat) { // Primero, omite los espacios iniciales. mat.usePattern(espacio);

mat.find( );

// Luego, obtiene la siguiente ficha de la cadena // al tratar de encontrar cada patrón.

// Se devuelve la ficha encontrada por el primer // patrón coincidente. Es importante el orden en que // se prueban los patrones. Si se revisa una palabra // antes que un número, podrían cambiar los resultados. // Primero, se busca un número.

mat.usePattern(numero);

if(mat.find( )) return mat.group( );

// Si no hay un número, se busca una palabra. mat.usePattern(palabra);

if(mat.find( )) return mat.group( );

// Si no hay una palabra, se busca un signo de puntuación. mat.usePattern(punt);

if(mat.find( )) return mat.group( );

// Por último, se busca el final de la cadena. mat.usePattern(fin);

// No se reconoce una ficha. return null; // ficha no válida }

// Demuestra la división en fichas.

public static void main(String args[ ]) { String ficha;

// Crea un Matcher.

Matcher mat = fin.matcher("La primera herramienta es un martillo," + " con un costo de $132.99.");

// Despliega las fichas de la cadena. do { ficha = obtenerFichaTexto(mat); if(ficha == null) { System.out.println("Ficha no v\u00a0lida"); break; } if(ficha.length( ) != 0) System.out.println("Ficha: " + ficha); else System.out.println("Final de la cadena"); } while(ficha.length( ) != 0); } }

Este programa produce la siguiente salida: Ficha: La Ficha: primera Ficha: herramienta Ficha: es Ficha: un Ficha: martillo Ficha: , Ficha: con Ficha: un Ficha: costo Ficha: de Ficha: $ Ficha: 132.99 Ficha: . Final de la cadena

DivisorSimpleDeTexto empieza por defi nir varios patrones que pueden usarse para dividir en fi chas texto en español (sin acentos), números o signos de puntuación. Tome nota de que cada patrón empieza con \G. Esto impone que cada patrón empiece donde se quedó la coincidencia anterior. Además, tome nota de que el patrón de palabra se especifi ca empleando \w. Por tanto, puede coincidir con letras y dígitos. En un momento verá por qué esto es importante.

A continuación está el método obtenerFichaTexto( ). Aquí es donde tiene lugar la división en fi chas. Al parámetro mat de obtenerFichaTexto( ) se le pasa un Matcher que contiene la cadena que habrá de dividirse. Empleando este Matcher, se trata de encontrar la siguiente fi cha de la cadena. Se hace esto al omitir primero los espacios en blanco iniciales. Luego, se prueba cada uno de los siguientes patrones en secuencia. Esto se hace al establecer primero el patrón empleado por mat al llamar a usePattern( ). Luego se llama a fi nd( ) en mat. Recuerde que fi nd( ) devuelve verdadero cuando encuentra una coincidencia, y falso si no la halla. Por tanto, si el primer patrón falla, se prueba el siguiente, etc. Se devuelve la fi cha encontrada por el primer patrón. Si se encuentra el fi nal de la cadena, entonces se devuelve una cadena de longitud cero. Si no se encuentran coincidencias, entonces se encontró alguna secuencia de caracteres que no es parte normal del texto en español, sin acentos, y se devuelve null.

Resulta importante comprender que el orden en que se prueban los patrones puede afectar la manera en que se encuentran las fi chas. En este ejemplo, el divisor prueba primero si hay coincidencias con el patrón de número antes de que prueba con palabras. Esto es necesario porque, para este ejemplo, se usó el patrón \w para defi nir el patrón palabra. Como sabe, la clase \w busca letras y dígitos. Por tanto, en el ejemplo, si busca palabras antes que números, entonces el número 132.99 se dividirá incorrectamente en tres fi chas: 132 (palabra), un punto (puntuación) y 99 (palabra). Por eso es necesario que la primera búsqueda sea de números. Por supuesto, en este ejemplo sería posible defi nir el patrón palabra para que excluya dígitos y evite esto, pero estás soluciones fáciles no siempre están disponibles. Por lo general, necesitará seleccionar cuidadosamente el orden en que se prueban los patrones.

Otro tema importante es que, para mayor claridad, obtenerFichaTexto( ) llama a usePattern( ) y fi nd( ) en dos instrucciones separadas. Por ejemplo:

mat.usePattern(numero);

if(mat.find( )) return mat.group( );

Sin embargo, esta secuencia puede escribirse así, de manera más compacta: if (mat.usePattern(numero).find( )) return mat.group( );

Esto funciona porque usePattern( ) devuelve el Matcher con el que opera. Esta forma más compacta es común en código profesional.

In document Java soluciones de programación (página 56-61)