代码之家  ›  专栏  ›  技术社区  ›  Ethan Heilman

在Java中帮助从字符串中解析数字的更好方法

  •  5
  • Ethan Heilman  · 技术社区  · 17 年前

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

    考虑字符串“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拆分两次,得到一个包含所有数字块的数组和一个包含所有字母块的数组。然后合并结果。我回避了这一点,因为它会损害可读性。

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

    • 调试人员处理得不好。
    • 它们中断了某人读取源代码的流程。
    • 加班雷鬼的有机成长,成为怪物。
    • 它们是非常不直观的。

    我的问题是:

    • 如何提高上述代码的可读性?
    • 有更好的方法吗?优雅地解决这个问题的Util类。
    • 在使用regex和将简单的东西编码成我上面写的东西之间,你在哪里划一条线?
    • 如何提高regex的可读性/可维护性?
    8 回复  |  直到 17 年前
        1
  •  5
  •   Alan Moore Chris Ballance    17 年前

    如果它意味着用一行代码解决问题,您愿意使用regex吗?

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

    通过解释regex的注释,我认为这比任何非regex解决方案(或者其他任何regex解决方案)都更易读。

        2
  •  13
  •   Joey Gumbo    17 年前

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

    您可能希望避免编写超过几行的正则表达式。这些可能是而且通常是不可读和难以理解的, 但是可以用它们替换的代码也是如此! 解析器几乎从来都不是漂亮的,你通常比试图理解生成的(或手写的)解析器更好地阅读原始语法。正则表达式(imho)也是如此,它只是对规则语法的一种简明描述。

    所以,总的来说,我认为禁止正则表达式支持您在问题中给出的代码听起来是一个非常愚蠢的想法。正则表达式只是一个工具,不只是工具,不只是工具。如果其他东西在文本解析方面做得更好(例如,真正的解析器、一些子字符串魔术等),那么就使用它。但是不要仅仅因为你对他们感到不舒服就放弃可能,其他人可能在处理这些问题上没有那么多问题,所有人都能学习。

    编辑:在mmyers评论后更新regex。

        3
  •  7
  •   brabster    17 年前

    对于实用程序类,请查看 java.util.Scanner . 关于如何解决你的问题,这里有很多选择。我对你的问题有几点看法。

    调试程序不能很好地处理它们(正则表达式)

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

    它们中断了某人读取源代码的流程。

    我想这取决于你和他们在一起有多舒服。就我个人而言,我宁愿阅读一个合理的regex,也不愿阅读超过50行的字符串解析代码,但这可能是一个私人问题。

    加班雷鬼的有机成长,成为怪物。

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

    它们是非常不直观的。

    它们是模式匹配语言。我想说,在这种情况下,它们是非常直观的。

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

    不确定,除了使用regex。

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

    如上所述,java.util.scanner。

    在使用regex和将简单的东西编码成我上面写的东西之间,你在哪里划一条线?

    就我个人而言,我使用regex做任何相当简单的事情。

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

    在扩展之前要仔细考虑,特别注意对代码和regex进行详细的注释,以便清楚地了解您在做什么。

        4
  •  2
  •   Juha Syrjälä    17 年前

    我将使用类似这样的代码(警告、未测试的代码)。对我来说,这比试图避免regexps更具可读性。在正确的位置使用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;
    }
    
        5
  •  1
  •   Bill K    17 年前

    我自己也不太喜欢regex,但这看起来像是一个他们会简化事情的例子。您可能想要做的是将它们放入您可以设计的最小方法中,恰当地命名它,然后将所有控制代码放入另一个方法中。

    例如,如果您编写了一个“抓取数字或字母块”方法,那么调用方将是一个非常简单的、直接的循环,只打印每个调用的结果,并且您调用的方法将被很好地定义,因此即使您对语法一无所知,regex的意图也将是清晰的,并且该方法将是有界的。这样人们就不会随着时间的推移把它弄脏。

    这方面的问题在于,regex工具非常简单,并且很好地适应了这种用法,因此很难证明方法调用是正确的。

        6
  •  1
  •   Michael Myers KitsuneYMG    17 年前

    既然还没有人发布正确的代码,我就试试看。

    首先是非regex版本。请注意,我使用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;
    }
    

    现在是regex版本。这基本上与juha s发布的代码相同,但regex实际上有效。

    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]
    
        7
  •  1
  •   Bert F    17 年前

    啊,有人打我编码。我认为regex版本更容易阅读/维护。另外,请注意两个实现的输出与预期输出之间的差异…

    输出:

    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]
    

    比较:

    digitsaelements.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"+"]");
        }
    
    }
    
        8
  •  1
  •   dfa    17 年前

    您可以使用这个类来简化您的循环:

    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这个类在其他上下文中也可以重用。