Question

J'ai une chaîne qui contient des chiffres et des lettres. Je veux diviser la chaîne en morceaux contigus de chiffres et de lettres blocs contigus.

Considérez la chaîne "34A312O5M444123A".

Je voudrais sortie: [ "34", "A", "312", "O", "5", "M", "444123", "A"]

J'ai le code qui fonctionne et ressemble à:

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

Je considérais diviser str deux fois pour obtenir un tableau contenant tous les morceaux numéros et un tableau contenant les morceaux tous lettres. fusionner ensuite les résultats. Je répugné qu'il en serait la lisibilité du mal.

Je l'ai intentionnellement évité résoudre ce avec un motif regex que je trouve des motifs regex d'être un obstacle majeur à la lisibilité.

  • débogueurs ne les gère pas bien.
  • Ils interrompent le flux de code source de lecture de quelqu'un.
  • Heures supplémentaires de regex croissance organique et deviennent des monstres.
  • Ils sont profondément non intuitive.

Mes questions sont les suivantes:

  • Comment pourrais-je améliorer la lisibilité du code ci-dessus?
  • Y at-il une meilleure façon de le faire? Une classe Util qui résout ce problème avec élégance.
  • Où tracer la ligne entre l'utilisation d'une expression régulière et le codage quelque chose simpilar à ce que j'ai écrit ci-dessus?
  • Comment augmenter la lisibilité / maintenabilité de Regexes?
Était-ce utile?

La solution

Seriez-vous prêt à utiliser regexes si cela signifiait résoudre le problème dans une ligne de code?

// 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)");

Avec le commentaire pour expliquer l'expression rationnelle, je pense que c'est plus lisible que l'une des solutions non-regex (ou l'une des autres solutions regex, pour cette matière).

Autres conseils

Pour cette tâche particulière que je avais toujours utiliser une expression régulière au lieu de quelque chose d'écriture main similaire. Le code que vous avez donné ci-dessus est, au moins pour moi, moins lisible qu'une simple expression régulière (qui serait (\d+|[^\d]+) dans ce cas, pour autant que je peux voir).

Vous pouvez éviter d'écrire des expressions régulières qui dépassent quelques lignes. Ceux-ci peuvent être et sont généralement illisibles et difficile à comprendre, mais si le code est qu'ils peuvent être remplacés par! Parsers sont presque jamais assez et vous êtes généralement mieux lire la grammaire originale que d'essayer de faire sens de l'analyseur généré (ou à la main). Même chose (AMHA) pour les expressions rationnelles qui sont juste une description concise d'une grammaire régulière.

Donc, en général, je dirais que l'interdiction regexes en faveur du code comme vous avez donné à votre question sonne comme une idée terriblement stupide. Et les expressions régulières sont juste un outil, rien de moins, rien de plus. Si quelque chose d'autre un meilleur travail de l'analyse de texte (par exemple, un analyseur réel, un peu de magie substring, etc.), utilisez-le. Mais ne jetez pas les possibilités juste parce que vous sentez mal à l'aise avec eux -. D'autres peuvent avoir moins de problèmes avec eux et aux prises toutes les personnes sont en mesure d'apprendre

EDIT:. Mise à jour regex après commentaire par mmyers

Pour une classe utilitaire, consultez java.util.Scanner . Il y a un certain nombre d'options là-dedans à la façon dont vous pourriez prendre pour résoudre votre problème. J'ai quelques commentaires sur vos questions.

  

débogueurs ne les gèrent pas (expressions régulières) et

Que ce soit un regex ou non des travaux dépend de ce qui est dans vos données. Il y a quelques plugins agréables que vous pouvez utiliser pour vous aider à construire un masque, comme QuickREx pour Eclipse, fait un débogueur réellement vous aider à écrire l'analyseur pour vos données?

  

Ils interrompent le flux de code source de lecture de quelqu'un.

Je suppose que cela dépend de la façon dont vous êtes à l'aise avec eux. Personnellement, je préfère lire un regex raisonnable que 50 autres lignes de code d'analyse syntaxique de la chaîne, mais peut-être est une chose personnelle.

  

Regex de temps supplémentaire croissance organique et deviennent des monstres.

Je suppose qu'ils pourraient, mais c'est probablement un problème avec le code qu'ils vivent à devenir non ciblées. Si la complexité des données source augmente, vous avez probablement besoin de garder un oeil sur si vous avez besoin d'une solution plus expressive (peut-être un générateur d'analyseur comme ANTLR)

  

Ils sont profondément non intuitive.

Ils sont une langue de correspondance de motif. Je dirais qu'ils sont assez intuitive dans ce contexte.

  

Comment pourrais-je améliorer la lisibilité du code ci-dessus?

Je ne sais pas, en dehors de l'utilisation d'une expression rationnelle.

  

Y at-il une meilleure façon de le faire? Une classe Util qui résout ce problème avec élégance.

Mentionnés ci-dessus, java.util.Scanner.

  

Où tracer la ligne entre l'utilisation d'une expression régulière et le codage quelque chose simpilar à ce que j'ai écrit ci-dessus?

Personnellement, j'utiliser regex pour quoi que ce soit raisonnablement simple.

  

Comment augmenter la lisibilité / maintenabilité de Regexes?

Réfléchissez bien avant d'étendre, prendre des précautions supplémentaires pour commenter le code et l'expression rationnelle en détail afin qu'il soit clair ce que vous faites.

Je voudrais utiliser quelque chose comme ça (avertissement, code non testé). Pour moi, cela est beaucoup plus facile à lire que d'essayer d'éviter regexps. Regexps sont un excellent outil lorsqu'il est utilisé en bonne place.

Commentant les méthodes et exemples de valeurs d'entrée et de sortie dans les commentaires aide également.

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

Je ne suis pas trop fou de moi-même regex, mais cela semble être un cas où ils simplifieront vraiment les choses. Ce que vous pouvez faire est de les mettre dans la plus petite méthode que vous pouvez imaginer, nommez justement, et puis mettre tout le code de commande dans une autre méthode.

Par exemple, si vous codé une méthode « bloc Grab de chiffres ou de lettres », l'appelant serait très simple, boucle droite avant impression que les résultats de chaque appel, et la méthode que vous appeliez serait bien défini de manière l'intention du regex serait clair, même si vous ne saviez pas quoi que ce soit sur la syntaxe et la méthode serait bornée pour que les gens ne seraient pas susceptibles de muck vers le haut au fil du temps.

Le problème est que les outils regex sont si simples et bien adapté à cette utilisation qu'il est difficile de justifier un appel de méthode pour cela.

Puisque personne ne semble avoir encore posté code correct, je vais vous donner un coup de feu.

D'abord la version non-regex. Notez que j'utilise le StringBuilder pour accumuler selon le type de caractère a été vu la dernière (chiffre ou non chiffres). Si les changements d'état, je largue son contenu dans la liste et de commencer une nouvelle StringBuilder. De cette façon, non-chiffres consécutifs sont groupés comme chiffres consécutifs sont.

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

Maintenant, la version regex. Ceci est essentiellement le même code qui a été publié par Juha S., mais l'expression rationnelle fonctionne réellement.

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

Une façon j'essaie de garder mes regexes lisible est leur nom. Je pense que DIGIT_OR_NONDIGIT_STRING traduit assez bien ce que je (le programmeur) pense qu'il fait, et les tests doivent se assurer qu'il fait vraiment ce qu'il est censé faire.

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

impressions:

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

Awww, quelqu'un m'a battu à code. Je pense que la version regex est plus facile à lire / maintenir. En outre, notez la différence de sortie entre les 2 implémentations vs la sortie attendue ...

Sortie:

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]

Comparer:

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

}

vous pouvez utiliser cette classe afin de simplifier votre boucle:

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

Maintenant, vous pouvez réécrire ceci:

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

avec:

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

mes 2 cents

BTW cette classe est réutilisable dans un autre contexte.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top