Java String.split () a veces dando cadenas en blanco

Estoy haciendo un rodillo de dados basado en texto. Toma cadenas como “2d10 + 5” y devuelve una cadena como resultado del rollo (s). Mi problema se muestra en el tokenizer que divide la cadena en partes útiles para que analice la información.

String[] tokens = message.split("(?=[dk\\+\\-])");

Esto está produciendo resultados extraños e inesperados. No sé exactamente qué los está causando. Podría ser la expresión regular, mi malentendido, o Java solo ser Java. Esto es lo que está pasando:

  • 3d6+4 produce la matriz de cadenas [3, d6, +4] . Esto es correcto.
  • d% produce la matriz de cadenas [d%] . Esto es correcto.
  • d20 produce la matriz de cadenas [d20] . Esto es correcto.
  • d%+3 produce la matriz de cadenas [, d%, +3] . Esto es incorrecto.
  • d20+2 produce la matriz de cadenas [, d20, +2] . Esto es incorrecto.

En el cuarto y quinto ejemplo, algo extraño está causando que aparezca una cadena vacía adicional al frente de la matriz. No es la falta de número en la parte delantera de la cadena, ya que otros ejemplos lo desmienten. No es la presencia del signo de porcentaje, ni el signo más.

Por ahora solo estoy continuando con el bucle for de strings en blanco, pero eso se parece a una solución de curita. ¿Alguien tiene alguna idea de lo que causa la cadena en blanco al frente de la matriz? ¿Cómo puedo arreglarlo?

Explorando el código fuente, obtuve el problema exacto detrás de este comportamiento.

El método Pattern.split() usa internamente Pattern.split() . El método de división antes de devolver la matriz resultante comprueba el último índice coincidente o si realmente hay una coincidencia. Si el último índice coincidente es 0 , es decir, su patrón coincidía con una cadena vacía al comienzo de la cadena o no coincidía en absoluto, en cuyo caso, la matriz devuelta es una única matriz de elementos que contiene el mismo elemento.

Aquí está el código fuente:

 public String[] split(CharSequence input, int limit) { int index = 0; boolean matchLimited = limit > 0; ArrayList matchList = new ArrayList(); Matcher m = matcher(input); // Add segments before each match found while(m.find()) { if (!matchLimited || matchList.size() < limit - 1) { String match = input.subSequence(index, m.start()).toString(); matchList.add(match); // Consider this assignment. For a single empty string match // m.end() will be 0, and hence index will also be 0 index = m.end(); } else if (matchList.size() == limit - 1) { // last one String match = input.subSequence(index, input.length()).toString(); matchList.add(match); index = m.end(); } } // If no match was found, return this if (index == 0) return new String[] {input.toString()}; // Rest of them is not required 

Si la última condición en el código anterior - index == 0 , es verdadera, entonces la matriz de elemento único se devuelve con la cadena de entrada.

Ahora, considere los casos en que el index puede ser 0 .

  1. Cuando no hay ninguna coincidencia en absoluto. (Como ya en el comentario anterior de esa condición)
  2. Si la coincidencia se encuentra al principio, y la longitud de la cadena coincidente es 0 , entonces el valor del índice en el bloque if (dentro del ciclo while) -

     index = m.end(); 

    será 0. La única cadena de coincidencia posible es una cadena vacía (longitud = 0). Cuál es exactamente el caso aquí. Y tampoco debería haber más coincidencias, de lo contrario el index se actualizaría a un índice diferente.

Entonces, teniendo en cuenta sus casos:

  • Para d% , solo hay una coincidencia única para el patrón, antes de la primera d . Por lo tanto, el valor del índice sería 0 . Pero como no hay más coincidencias, el valor del índice no se actualiza, y la condición if se convierte en true , y devuelve la matriz de elemento único con la cadena original.

  • Para d20+2 , habría dos coincidencias, una antes de d y una antes de + . Por lo tanto, el valor del índice se actualizará y, por lo tanto, se devolverá ArrayList en el código anterior, que contiene la cadena vacía como resultado de la división del delimitador, que es el primer carácter de la cadena, como ya se explicó en la respuesta de @ Stema.

Entonces, para obtener el comportamiento que desea (que se divide en delimitador solo cuando no está al principio, puede agregar un aspecto negativo detrás en su patrón de expresiones regulares):

 "(? 

esto se dividirá en una cadena vacía seguida de su clase de caracteres, pero no precedida por el comienzo de la cadena.


Considere el caso de dividir la cadena "ad%" en el patrón de expresiones regulares - "a(?=[dk+-])" . Esto le dará una matriz con el primer elemento como cadena vacía. Cuál es el único cambio aquí, la cadena vacía se reemplaza con a :

 "ad%".split("a(?=[dk+-])"); // Prints - `[, d%]` 

¿Por qué? Eso es porque la longitud de la cadena coincidente es 1 . Por lo tanto, el valor del índice después de la primera coincidencia - m.end() no sería 0 sino 1 , y por lo tanto la matriz de elemento único no será devuelta.

Me sorprendió que no ocurra en los casos 2 y 3, por lo que la verdadera pregunta aquí es

¿Por qué no hay una cadena vacía al inicio para “d20” y “d%”?

como Rohit Jain explicó en sus análisis detallados, esto sucede, cuando solo se encuentra una coincidencia al comienzo de la cadena y el índice match.end es 0. (Esto solo puede suceder, cuando solo se utiliza una aserción de búsqueda para encontrar la partido).

El problema es que d%+3 comienza con un carácter que está dividiendo. Entonces tu expresión regular coincide con el primer carácter y obtienes una cadena vacía al comienzo.

Puede agregar un lookbehind, para asegurarse de que su expresión no coincida al comienzo de la cadena, para que no se divida allí:

 String[] tokens = message.split("(? 

(? es una afirmación de mirar hacia atrás que es verdadera, cuando no está al comienzo de la cadena.

Recomendaría la coincidencia simple en lugar de la división:

 Matcher matcher = Pattern.compile("([1-9]*)(d[0-9%]+)([+-][0-9]+)?").matcher(string); if(matcher.matches()) { String first = matcher.group(1); // etc } 

No hay garantía para la expresión regular, pero creo que sí …