Pregunta

Tengo una cadena que contiene números y letras. Deseo dividir la cadena en trozos contiguos de dígitos y trozos contiguos de letras.

Considere la cadena "34A312O5M444123A".

Me gustaría de salida: [ "34", "A", "312", "O", "5", "M", "444123", "A"]

Tengo código que funciona y se parece a:

List<String> digitsAsElements(String str){
  StringBuilder digitCollector = new StringBuilder();

  List<String> output = new ArrayList<String>();

  for (int i = 0; i < str.length(); i++){
    char cChar = str.charAt(i);

    if (Character.isDigit(cChar))
       digitCollector.append(cChar);
    else{
      output.add(digitCollector.toString());
      output.add(""+cChar);

      digitCollector = new StringBuilder();
    }         
  }

  return output;
}

consideré división str dos veces para obtener una matriz que contiene todos los trozos números y una matriz que contiene los trozos Todas las cartas. A continuación, la fusión de los resultados. Me rehuido esto, ya que perjudicaría la legibilidad.

He evitado intencionadamente resolver esto con un patrón de expresión ya que encontrar patrones de expresiones regulares para ser un gran impedimento para la legibilidad.

  • Depuradores no manejar bien.
  • Se interrumpen el flujo de alguien que lee el código fuente.
  • Las horas extraordinarias expresiones regulares de crecer de forma orgánica y se convierten en monstruos.
  • Son profundamente no intuitiva.

Mis preguntas son:

  • ¿Cómo podría mejorar la legibilidad del código anterior?
  • ¿Hay una mejor manera de hacer esto? Una clase Util que resuelve este problema con elegancia.
  • ¿Dónde se traza la línea entre el uso de una expresión regular y codificación algo simpilar a lo que he escrito más arriba?
  • ¿Cómo aumentar la legibilidad / mantenimiento de expresiones regulares?
¿Fue útil?

Solución

¿Estaría dispuesto a utilizar expresiones regulares si eso significaba la solución del problema en una línea de código?

// Split at any position that's either:
// preceded by a digit and followed by a non-digit, or
// preceded by a non-digit and followed by a digit.
String[] parts = str.split("(?<=\\d)(?=\\D)|(?<=\\D)(?=\\d)");

Con el comentario para explicar la expresión regular, creo que es más fácil de leer que cualquiera de las soluciones no expresiones regulares (o cualquiera de las otras soluciones de expresiones regulares, para el caso).

Otros consejos

Para esta tarea en particular que siempre había usar una expresión regular en lugar de-escritura a mano algo similar. El código que ha dado anteriormente es, al menos para mí, menos legible que una simple expresión regular (que sería (\d+|[^\d]+) en este caso, por lo que yo puedo ver).

Es posible que desee evitar la escritura de expresiones regulares que exceden unas pocas líneas. Estos pueden ser y generalmente son ilegibles y difícil de entender, pero también lo es el código que pueden ser reemplazados con! Los analizadores son casi nunca bastante y ya está por lo general mejor que la lectura de la gramática original que tratar de hacer sentido de que el analizador generado (o escrita a mano). Lo mismo va (en mi humilde opinión) para expresiones regulares que son sólo una descripción concisa de una gramática regular.

Así que, en general, yo diría que la prohibición de expresiones regulares en favor de código como usted ha dado en su pregunta suena como una idea tremendamente estúpida. Y las expresiones regulares son sólo una herramienta, nada menos, nada más. Si hay algo que lo hace un mejor trabajo de análisis de texto (por ejemplo, un programa de análisis real, algo de magia subcadena, etc) y luego usarlo. Pero no se deshaga de posibilidades sólo porque se siente incómodo con ellos -. Que otros pueden tener menos problemas para hacer frente con ellos y todas las personas son capaces de aprender

EDIT:. Actualizado expresiones regulares tras las observaciones de mmyers

Para una clase de utilidad, echa un vistazo a java.util.Scanner. Hay una serie de opciones en cuanto a la forma no es posible ir sobre la solución de su problema. Tengo algunos comentarios sobre sus preguntas.

  

Depuradores no manejan ellos (expresiones regulares) así

Ya sea una fábrica de expresiones regulares o no depende de lo que hay en sus datos. Hay algunos plugins agradables que puede utilizar para ayudarle a construir una expresión regular, como QuickREx para Eclipse, tiene un depurador en realidad ayudarle a escribir el analizador adecuado para sus datos?

  

Se interrumpen el flujo de alguien que lee el código fuente.

Me supongo que depende de cuán cómodo se siente con ellos. En lo personal, prefiero leer una expresión regular razonable de 50 líneas más de código de secuencia de análisis, pero tal vez eso es una cosa personal.

  

Las horas extraordinarias expresiones regulares de crecer de forma orgánica y se convierten en monstruos.

supongo que podría, pero eso es probablemente un problema con el código que viven en convertirse en desenfocados. Si la complejidad de los datos de origen es cada vez mayor, es probable que tenga que mantener un ojo sobre si necesita una solución más expresivo (tal vez un generador de analizadores sintácticos como antlr)

  

Están profundamente no intuitiva.

Son un lenguaje de patrones. Yo diría que son bastante intuitivo en ese contexto.

  

¿Cómo podría mejorar la legibilidad del código anterior?

No está seguro, aparte de su uso una expresión regular.

  

¿Hay una mejor manera de hacer esto? Una clase Util que resuelve este problema con elegancia.

mencionado anteriormente, java.util.Scanner.

  

¿Dónde se traza la línea entre el uso de una expresión regular y codificación algo simpilar a lo que he escrito más arriba?

En lo personal yo uso de expresiones regulares para cualquier cosa razonablemente simple.

  

¿Cómo aumentar la legibilidad / mantenimiento de expresiones regulares?

Piense cuidadosamente antes de extender, tener un cuidado especial para comentar el código y la expresión regular en detalle para que sea más claro lo que está haciendo.

Me gustaría utilizar algo como esto (la advertencia, el código no probado). Para mí esto es mucho más fácil de leer que tratar de evitar las expresiones regulares. Expresiones regulares son una gran herramienta cuando se utiliza en lugar correcto.

Comentando métodos y proporcionar ejemplos de valores de entrada y de salida en los comentarios también ayuda.

List<String> digitsAsElements(String str){
    Pattern p = Pattern.compile("(\\d+|\\w+)*");
    Matcher m = p.matcher(str);

    List<String> output = new ArrayList<String>();
    for(int i = 1; i <= m.groupCount(); i++) {
       output.add(m.group(i));
    }
    return output;
}

No estoy demasiado loca por regex a mí mismo, pero esto parece como un caso en el que realmente va a simplificar las cosas. ellos lo que es posible que desee hacer es poner en el método más pequeño que puede concebir, por nombrar acertadamente, y luego poner todo el código de control en otro método.

Por ejemplo, si ha codificado un "bloque Grab de números o letras" método, la persona que llama sería una muy simple bucle, recta hacia adelante simplemente la impresión de los resultados de cada llamada, y el método estuviera llamando sería bien definido por lo que la intención de la expresión regular sería clara, incluso si usted no sabe nada acerca de la sintaxis y el método podría estar limitado por lo que la gente no sería probable que lodo se acumula con el tiempo.

El problema con esto es que las herramientas de expresiones regulares son tan simples y bien adaptado a este uso, que es difícil de justificar una llamada a un método para esto.

Ya que nadie parece haber publicado código correcto, sin embargo, voy a darle un tiro.

En primer lugar la versión no expresiones regulares. Nótese que uso el StringBuilder para acumular cualquier tipo de personaje fue visto por última vez (dígito o no dígitos). Los cambios de estado si, yo tiro su contenido en la lista y empezar un nuevo StringBuilder. De esta manera no consecutiva dígitos se agrupan como dígitos consecutivos son.

static List<String> digitsAsElements(String str) {
    StringBuilder collector = new StringBuilder();

    List<String> output = new ArrayList<String>();
    boolean lastWasDigit = false;
    for (int i = 0; i < str.length(); i++) {
        char cChar = str.charAt(i);

        boolean isDigit = Character.isDigit(cChar);
        if (isDigit != lastWasDigit) {
            if (collector.length() > 0) {
                output.add(collector.toString());
                collector = new StringBuilder();
            }
            lastWasDigit = isDigit;
        }
        collector.append(cChar);
    }
    if (collector.length() > 0)
        output.add(collector.toString());

    return output;
}

Ahora la versión de expresiones regulares. Este es básicamente el mismo código que fue publicada por Juha S., pero la expresión regular realmente funciona.

private static final Pattern DIGIT_OR_NONDIGIT_STRING =
        Pattern.compile("(\\d+|[^\\d]+)");
static List<String> digitsAsElementsR(String str) {
    // Match a consecutive series of digits or non-digits
    final Matcher matcher = DIGIT_OR_NONDIGIT_STRING.matcher(str);
    final List<String> output = new ArrayList<String>();
    while (matcher.find()) {
        output.add(matcher.group());
    }
    return output;
}

Una forma Trato de mantener mis expresiones regulares es legible por sus nombres. Creo DIGIT_OR_NONDIGIT_STRING transmite bastante bien lo que (el programador) que lo hace, y la prueba debe asegurarse de que lo que realmente hace lo que se supone que debe hacer.

public static void main(String[] args) {
    System.out.println(digitsAsElements( "34A312O5MNI444123A"));
    System.out.println(digitsAsElementsR("34A312O5MNI444123A"));
}

impresiones:

[34, A, 312, O, 5, MNI, 444123, A]
[34, A, 312, O, 5, MNI, 444123, A]

Awww, alguien se me adelantó código. Creo que la versión de expresiones regulares es más fácil de leer / mantener. Además, tenga en cuenta la diferencia de salida entre las implementaciones 2 vs la salida esperada ...

Salida:

digitsAsElements1("34A312O5MNI444123A") = [34, A, 312, O, 5, M, , N, , I, 444123, A]
digitsAsElements2("34A312O5MNI444123A") = [34, A, 312, O, 5, MNI, 444123, A]
Expected: [34, A, 312, O, 5, MN, 444123, A]

Comparación:

DigitsAsElements.java:

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class DigitsAsElements {

    static List<String> digitsAsElements1(String str){
        StringBuilder digitCollector = new StringBuilder();

        List<String> output = new ArrayList<String>();

        for (int i = 0; i < str.length(); i++){
          char cChar = str.charAt(i);

          if (Character.isDigit(cChar))
             digitCollector.append(cChar);
          else{
            output.add(digitCollector.toString());
            output.add(""+cChar);

            digitCollector = new StringBuilder();
          }         
        }

        return output;
      }

    static List<String> digitsAsElements2(String str){
        // Match a consecutive series of digits or non-digits
        final Pattern pattern = Pattern.compile("(\\d+|\\D+)");
        final Matcher matcher = pattern.matcher(str);

        final List<String> output = new ArrayList<String>();
        while (matcher.find()) {
            output.add(matcher.group());
        }

        return output;
      }

    /**
     * @param args
     */
    public static void main(String[] args) {
        System.out.println("digitsAsElements(\"34A312O5MNI444123A\") = " +
                digitsAsElements1("34A312O5MNI444123A"));
        System.out.println("digitsAsElements2(\"34A312O5MNI444123A\") = " +
                digitsAsElements2("34A312O5MNI444123A"));
        System.out.println("Expected: [" +
                "34, A, 312, O, 5, MN, 444123, A"+"]");
    }

}

podría utilizar esta clase con el fin de simplificar su bucle:

public class StringIterator implements Iterator<Character> {

    private final char[] chars;
    private int i;

    private StringIterator(char[] chars) {
        this.chars = chars;
    }

    public boolean hasNext() {
        return i < chars.length;
    }

    public Character next() {
        return chars[i++];
    }

    public void remove() {
        throw new UnsupportedOperationException("Not supported.");
    }

    public static Iterable<Character> of(String string) {
        final char[] chars = string.toCharArray();

        return new Iterable<Character>() {

            @Override
            public Iterator<Character> iterator() {
                return new StringIterator(chars);
            }
        };
    }
}

Ahora puede volver a escribir esto:

for (int i = 0; i < str.length(); i++){
    char cChar = str.charAt(i);
    ...
}

por:

for (Character cChar : StringIterator.of(str)) {
    ...
}

mis 2 centavos de dólar

Por cierto, esta clase también es reutilizable en otro contexto.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top