帮助更好地解析Java中String的数字

时间:2009-06-04 19:25:31

标签: java regex string readability literate-programming

我有一个包含数字和字母的字符串。我希望将字符串分成连续的数字块和连续的字母块。

考虑字符串“34A312O5M444123A”。

我想输出: [“34”,“A”,“312”,“O”,“5”,“M”,“444123”,“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;
}

我考虑将str拆分两次以获得包含所有数字块的数组和包含所有字母块的数组。然后合并结果。我避开了它,因为它会损害可读性。

我故意避免用正则表达式模式解决这个问题,因为我发现正则表达式模式是可读性的主要障碍。

  • 调试器无法很好地处理它们。
  • 他们打断了某人阅读源代码的流程。
  • 加时正则表达式有机地成长并成为怪物。
  • 他们非常不直观。

我的问题是:

  • 如何提高上述代码的可读性?
  • 有更好的方法吗?一个优雅地解决这个问题的Util类。
  • 你在哪里使用regEx和编写与上面所写内容相似的内容之间划清界限?
  • 如何提高regExes的可读性/可维护性?

8 个答案:

答案 0 :(得分:13)

对于这个特殊的任务,我总是使用正则表达式而不是手写类似的东西。至少对我来说,上面给出的代码比简单的正则表达式(在这种情况下(\d+|[^\d]+)可读性更低,据我所见)。

您可能希望避免编写超过几行的正则表达式。那些可能并且通常是不可读的并且难以理解,但代码也可以被替换掉!解析器几乎从不漂亮而且你通常更喜欢阅读原始语法而不是试图制作生成(或手写)解析器的意义。同样的(imho)用于正则表达式,它只是对常规语法的简明描述。

所以,总的来说,我会说禁止正则表达式支持你在问题中给出的代码听起来像一个非常愚蠢的想法。正则表达式只是一种工具,仅此而已,仅此而已。如果其他东西能更好地进行文本解析(例如,真正的解析器,某些子串魔法等),那么就使用它。但是不要因为你对他们感到不舒服而抛弃可能性 - 其他人可能在处理这些问题方面遇到的问题较少,并且所有人都能够学习。

编辑:在mmyers发表评论后更新了正则表达式。

答案 1 :(得分:7)

对于实用程序类,请查看java.util.Scanner。关于如何解决问题,有很多选择。我对你的问题有一些评论。

  

调试器不能正确处理它们(正则表达式)

正则表达式是否有效取决于数据中的内容。您可以使用一些很好的插件来帮助您构建正则表达式,例如Eclipse的QuickREx,调试器实际上是否可以帮助您为数据编写正确的解析器?

  

他们打断了某人阅读源代码的流程。

我想这取决于你对他们的舒适程度。就个人而言,我宁愿阅读一个合理的正则表达式而不是50多行字符串解析代码,但也许这是个人的事情。

  

加时正则表达式有机地成长并成为怪物。

我猜他们可能,但这可能是他们生活的代码变得无关紧要的问题。如果源数据的复杂性在增加,您可能需要密切关注是否需要更具表现力的解决方案(可能是像ANTLR这样的解析器生成器)

  

他们非常不直观。

他们是模式匹配语言。我会说他们在这种情况下非常直观。

  

如何提高上述代码的可读性?

不确定,除了使用正则表达式。

  

有更好的方法吗?一个优雅地解决这个问题的Util类。

上面提到过,java.util.Scanner。

  

你在哪里使用regEx和编写与我上面所写的内容相似的内容之间划清界限?

我个人使用正则表达式来做任何相当简单的事情。

  

如何提高regExes的可读性/可维护性?

在扩展之前要仔细考虑,特别注意对代码和正则表达式进行详细评论,以便明确你正在做什么。

答案 2 :(得分:5)

如果它意味着在一行代码中解决问题,您是否愿意使用正则表达式?

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

通过评论来解释正则表达式,我认为这比任何非正则表达式解决方案(或任何其他正则表达式解决方案)更具可读性。

答案 3 :(得分:2)

我会使用这样的东西(警告,未经测试的代码)。对我来说,这比试图避免正则表达式更具可读性。在正确的位置使用时,Regexps是一个很好的工具。

评论方法并在评论中提供输入和输出值的示例也有帮助。

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

答案 4 :(得分:1)

Awww,有人打我代码。我认为正则表达式版本更容易阅读/维护。另外,请注意2个实现与预期输出之间的输出差异......

<强>输出:

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]

<强>比较

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

}

答案 5 :(得分:1)

您可以使用此类来简化循环:

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

现在你可以改写这个:

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

使用:

for (Character cChar : StringIterator.of(str)) {
    ...
}
我的2美分

BTW这个类在其他情况下也可以重复使用。

答案 6 :(得分:1)

我自己并没有对正则表达式过于疯狂,但这似乎是一个真正简化事物的案例。您可能想要做的是将它们放入您可以设计的最小方法中,恰当地命名,然后将所有控制代码放在另一种方法中。

例如,如果您编写了“Grab数字或字母块”方法,则调用者将是一个非常简单,直接的循环,只需打印每个调用的结果,并且您调用的方法将很好 - 定义所以即使你对语法一无所知,正则表达式的意图也会很清楚,并且这个方法会受到限制,所以人们不会随着时间的推移而搞砸它。

问题在于,正则表达式工具非常简单,并且很适合这种用法,因此很难证明方法调用的合理性。

答案 7 :(得分:1)

由于似乎没有人发布正确的代码,我会试一试。

首先是非正则表达式版本。请注意,我使用StringBuilder来累积最后看到的任何类型的字符(数字或非数字)。如果状态发生变化,我将其内容转储到列表中并启动一个新的StringBuilder。这样,连续的非数字就像连续的数字一样被分组。

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

现在是正则表达式版本。这与Juha S.发布的代码基本相同,但正则表达式实际上有效。

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

我试图让我的正则表达式可读的一种方式是他们的名字。我认为DIGIT_OR_NONDIGIT_STRING很好地表达了我(程序员)认为它所做的事情,并且测试应该确保它确实能够完成它的意图。

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

打印:

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