Pergunta

Eu tenho uma string que contém números e letras. Quero dividir a string em pedaços contíguos de dígitos e blocos contíguos de letras.

Considere a String "34A312O5M444123A".

Gostaria de saída: [ "34", "A", "312", "O", "5", "M", "444123", "A"]

Eu tenho o código que funciona e se parece com:

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;
}

Eu considerei divisão str duas vezes para obter um array contendo todos os números pedaços e um array contendo os pedaços todas as letras. Em seguida, a fusão dos resultados. Eu evitava isso, pois iria prejudicar a legibilidade.

Eu intencionalmente evitado resolver isso com um padrão de regex como eu encontrar padrões de regex para ser um grande impedimento para a legibilidade.

  • Os depuradores não tratá-los bem.
  • Eles interrompem o fluxo de código de alguém fonte de leitura.
  • O Overtime regex crescer organicamente e tornar-se monstros.
  • Eles são profundamente não intuitiva.

As minhas perguntas são:

  • Como eu poderia melhorar a legibilidade do código acima?
  • Existe uma maneira melhor de fazer isso? classe A Util que resolve esse problema elegantemente.
  • Onde você desenhar a linha entre usar um regex e codificação algo simpilar para o que eu escrevi acima?
  • Como você aumentar a legibilidade / manutenção de expressões regulares?
Foi útil?

Solução

Você estaria disposto a usar expressões regulares que isso significasse resolver o problema em uma linha 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)");

Com o comentário para explicar a regex, eu acho que é mais legível do que qualquer uma das soluções não-regex (ou qualquer uma das outras soluções regex, para que o assunto).

Outras dicas

Para esta tarefa especial que eu sempre usar um regex em vez de escrever-mão algo semelhante. O código que você tem dado acima é, pelo menos para mim, menos legível do que uma expressão regular (o que seria (\d+|[^\d]+) neste caso, tanto quanto eu posso ver).

Você pode querer evitar escrever expressões regulares que excedem algumas linhas. Aqueles podem ser, e geralmente são ilegíveis e difícil de entender, mas assim é o código que pode ser substituído por! Analisadores quase nunca são bonita e é geralmente melhor leitura da gramática original do que tentar fazer sentido do analisador gerado (ou escrita à mão). O mesmo vale (IMHO) para expressões regulares que são apenas uma descrição concisa de uma gramática regular.

Assim, em I geral diria que proíbe expressões regulares em favor de um código como você deu em seus sons de interrogação como uma idéia terrivelmente estúpido. E as expressões regulares são apenas uma ferramenta, nada menos, nada mais. Se alguma coisa faz um trabalho melhor de análise de texto (digamos, um verdadeiro analisador, alguma magia substring, etc), então usá-lo. Mas não jogue fora possibilidades só porque você se sentir desconfortável com eles -. Outros podem ter menos problemas de enfrentamento com eles e todas as pessoas são capazes de aprender

EDIT:. Atualizado regex após comentário por mmyers

Para uma classe de utilitário, consulte a java.util.Scanner. Há uma série de opções de lá a respeito de como você pode ir sobre como resolver o seu problema. Eu tenho alguns comentários sobre suas perguntas.

Os depuradores não lidar com eles (expressões regulares) bem

Quer obras um regex ou não, depende o que está em seus dados. Há alguns bons plugins que você pode usar para ajudá-lo a construir um regex, como QuickREx para Eclipse, não um depurador realmente ajudá-lo a escrever o analisador certa para seus dados?

Eles interrompem o fluxo de código de alguém fonte de leitura.

Eu acho que depende de como você está confortável com eles. Pessoalmente, eu prefiro ler um regex razoável do que mais de 50 linhas de código seqüência de análise, mas talvez isso é uma coisa pessoal.

O Overtime regex crescer organicamente e tornar-se monstros.

Eu acho que eles poderiam, mas isso é provavelmente um problema com o código vivem em se tornar desfocada. Se a complexidade dos dados de origem está aumentando, você provavelmente terá que manter um olho sobre se você precisa de uma solução mais expressivo (talvez um gerador de analisador como ANTLR)

Eles são profundamente não intuitiva.

Eles são uma linguagem padrão de correspondência. Eu diria que eles são bastante intuitivo nesse contexto.

Como eu poderia melhorar a legibilidade do código acima?

Não tenho certeza, além do uso de uma expressão regular.

Existe uma maneira melhor de fazer isso? classe A Util que resolve esse problema elegantemente.

mencionado acima, java.util.Scanner.

Onde você desenhar a linha entre usar um regex e codificação algo simpilar para o que eu escrevi acima?

Pessoalmente eu uso regex para qualquer coisa razoavelmente simples.

Como você aumentar a legibilidade / manutenção de expressões regulares?

Pense cuidadosamente antes de estender, tomar cuidado extra para comentar o código ea regex em detalhe de modo que é claro o que você está fazendo.

Gostaria de usar algo como isto (aviso, código não testado). Para mim, isso é muito mais legível do que tentar evitar regexps. Regexps são uma ótima ferramenta quando usado no lugar certo.

comentar métodos e proporcionando exemplos de valores de entrada e de saída em observações também ajuda.

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;
}

Estou não excessivamente sobre louco me regex, mas este parece ser um caso onde vão as coisas realmente simplificar. O que você pode querer fazer é colocá-los no menor método que você pode conceber, nomeá-lo apropriadamente, e, em seguida, colocar todo o código de controle em outro método.

Por exemplo, se você codificou um "bloco Grab de números ou letras" método, o chamador seria um circuito muito simples, direta apenas imprimir os resultados de cada chamada, e o método que estavam chamando seria bem definido de modo a intenção do regex seria clara, mesmo se você não sabe nada sobre a sintaxe eo método seria limitado para que as pessoas não seria provável que muck-lo ao longo do tempo.

O problema com isso é que as ferramentas de regex são tão simples e bem adaptado para este uso que é difícil justificar uma chamada de método para isso.

Uma vez que ninguém parece ter postado código correto ainda, vou dar-lhe um tiro.

Primeiro, a versão não-regex. Note que eu uso o StringBuilder para acumular qualquer tipo de personagem foi visto pela última vez (dígitos ou não-dígito). Se o estado muda, eu despejar o seu conteúdo para a lista e começar um novo StringBuilder. Desta forma não-dígitos consecutivos são agrupados como dígitos consecutivos são.

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;
}

Agora, a versão regex. Este é basicamente o mesmo código que foi escrito por Juha S., mas a regex 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;
}

Uma maneira que eu tento manter meus regexes legível é seus nomes. Acho que transmite DIGIT_OR_NONDIGIT_STRING muito bem o que eu (o programador) acha que ele faz, e os testes devem se certificar de que ele realmente faz o que pretendia fazer.

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

impressões:

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

Awww, alguém me bater para o código. Eu acho que a versão regex é mais fácil de ler / manter. Além disso, observe a diferença na saída entre os 2 implementações vs o resultado esperado ...

Output:

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]

Compare:

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"+"]");
    }

}

Você pode usar essa classe, a fim de simplificar o seu loop:

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);
            }
        };
    }
}

Agora você pode reescrever isso:

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

com:

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

meus 2 centavos

BTW esta classe também é reutilizável em outro contexto.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top