【问题标题】:Java regex input from txt file来自 txt 文件的 Java 正则表达式输入
【发布时间】:2017-01-06 09:05:06
【问题描述】:

我有一个包含一些数学表达式的文本文件。 我需要使用正则表达式将文本解析为组件(单词、句子、标点符号、数字和算术符号),计算数学表达式并使用计算出的数字表达式以原始形式返回文本。 我在没有正则表达式的情况下完成了这个(没有计算)。现在我正在尝试使用正则表达式来做到这一点。 我不完全理解如何正确地做到这一点。输入文字是这样的:

Pete 喜欢数学 5+3 和 jesica too sin(3)。

在我需要的输出中:

Pete 喜欢数学 8,jesica 也喜欢 0,14。

我需要一些知道如何做到这一点的人的正则表达式和计算方面的建议。

我的代码:

final static Pattern PUNCTUATION = Pattern.compile("([\\s.,!?;:]){1,}");
final static Pattern LETTER = Pattern.compile("([а-яА-Яa-zA-Z&&[^sin]]){1,}");
    List<Sentence> sentences = new ArrayList<Sentence>();
    List<PartOfSentence> parts = new ArrayList<PartOfSentence>();
    StringTokenizer st = new StringTokenizer(text, " \t\n\r:;.!?,/\\|\"\'",
            true);

带有正则表达式的代码(不起作用):

while (st.hasMoreTokens()) {

        String s = st.nextToken().trim();
        int size = s.length();
        for (int i=0; i<s.length();i++){
        //with regex. not working variant
        Matcher m = LETTER.matcher(s);
        if (m.matches()){
            parts.add(new Word(s.toCharArray()));
        }
        m = PUNCTUATION.matcher(s);
        if (m.matches()){
            parts.add(new Punctuation(s.charAt(0)));
        }
        Sentence buf = new Sentence(parts);
        if (buf.getWords().size() != 0) {
            sentences.add(buf);
            parts = new ArrayList<PartOfSentence>();
        } else
            parts.add(new Punctuation(s.charAt(0)));

没有正则表达式(工作):

if (size < 1)
            continue;
        if (size == 1) {
            switch (s.charAt(0)) {
            case ' ':               
                continue;
            case ',':
            case ';':
            case ':':
            case '\'':
            case '\"':
                parts.add(new Punctuation(s.charAt(0)));
                break;
            case '.':
            case '?':
            case '!':
                parts.add(new Punctuation(s.charAt(0)));
                Sentence buf = new Sentence(parts);
                if (buf.getWords().size() != 0) {
                    sentences.add(buf);
                    parts = new ArrayList<PartOfSentence>();
                } else
                    parts.add(new Punctuation(s.charAt(0)));
                break;
            default:
                parts.add(new Word(s.toCharArray()));
            }

        } else {
            parts.add(new Word(s.toCharArray()));
        }
    }

【问题讨论】:

    标签: java regex parsing text


    【解决方案1】:

    这不是一个容易解决的问题,因为即使匹配的数字也会变得相当复杂。

    首先,可以通过正则表达式"(\\d*(\\.\\d*)?\\d(e\\d+)?)"匹配一个数字,以考虑小数位和指数格式。

    其次,您要解决(至少)三种类型的表达式:二元、一元和函数。对于每一个,我们创建一个模式来匹配solve 方法。

    第三,有许多库可以实现reduce 方法,例如thisthis

    下面的实现不处理嵌套表达式,例如 sin(5) + cos(3) 或表达式中的空格。

    private static final String NUM = "(\\d*(\\.\\d*)?\\d(e\\d+)?)";
    
    public String solve(String expr) {
        expr = solve(expr, "(" + NUM + "(!|\\+\\+|--))"); //unary operators
        expr = solve(expr, "(" + NUM + "([+-/*]" + NUM + ")+)"); // binary operators
        expr = solve(expr, "((sin|cos|tan)\\(" + NUM + "\\))"); // functions
    
        return expr;
    }
    
    private String solve(String expr, String pattern) {
        Matcher m = Pattern.compile(pattern).matcher(expr);
    
        // assume a reduce method :String -> String that solve expressions 
        while(m.find()){
            expr = m.replaceAll(reduce(m.group()));
        }
        return expr;
    }
    
    //evaluate expression using exp4j, format to 2 decimal places, 
    //remove trailing 0s and dangling decimal point
    private String reduce(String expr){
        double res = new ExpressionBuilder(expr).build().evaluate();
        return String.format("%.2f",res).replaceAll("0*$", "").replaceAll("\\.$", ""); 
    }
    

    【讨论】:

    • 嘿!谢谢你的回答。我不完全了解如何在我的项目中实现 reduce 方法。你能给我举个小例子吗?
    • 有许多库给定字符串 3+4 评估表达式并返回 7。我使用其中一个库 exp4j 更新了答案。在reduce 中,您可以实现您想要评估单个表达式的方式
    • 谢谢!很酷的库,通过这种方式,一切正常。
    【解决方案2】:

    我认为您可以从在输入字符串中查找“功能”匹配开始。那么所有不匹配的函数就直接返回。

    例如,我希望这个短代码可以满足您的需求:

    带有 Main 方法的类。

    public class App {
        StringTokenizer st = new StringTokenizer("Pete likes Mathematics 3+3 and Jessica too 6+3.", " \t\n\r:;.!?,/\\|\"\'", true);
    
        public static void main(String[] args) {
            new App();
        }
        public App(){
            ArrayList<String> renderedStrings = new ArrayList<String>();
            while(st.hasMoreTokens()){
                String s = st.nextToken();
                if(!AdditionPatternFuntion.render(s, renderedStrings)){
                    renderedStrings.add(s);
                }
            }
            for(String s : renderedStrings){
                System.out.print(s);
            }
        }   
    }
    

    完成真正工作的“AdditionPattern”类

    import java.util.ArrayList;
    import java.util.StringTokenizer;
    import java.util.regex.Pattern;
    
    class AdditionPatternFuntion{
        public static boolean render(String s, ArrayList<String> renderedStrings){
            Pattern pattern = Pattern.compile("(\\d\\+\\d)");
            boolean match = pattern.matcher(s).matches();
            if(match){
                StringTokenizer additionTokenier = new StringTokenizer(s, "+", false);
                Integer firstOperand = new Integer(additionTokenier.nextToken());
                Integer secondOperand = new Integer(additionTokenier.nextToken());
                renderedStrings.add(new Integer(firstOperand + secondOperand).toString());
            }
            return match;
        }
    }
    

    当我使用这个输入运行时:

    Pete 喜欢数学 3+3,而 Jessica 也喜欢 6+3。

    我得到这个输出:

    Pete 喜欢数学 6,而 Jessica 也喜欢 9。

    要处理“sin()”函数,您可以执行相同的操作:创建一个新类,例如“SinPatternFunction”,然后执行相同操作。

    我认为您甚至应该创建一个抽象类“FunctionPattern”,其中包含一个抽象方法“render”,您将使用 AssitionPatternFunction 和 SinPatternFunction 类来实现它。 最后,您将能够创建一个类,我们称之为“PatternFunctionHandler”,它将创建一个 PatternFunction 列表(一个 SinPatternFunction、一个 AdditionPatternFunction (等等))然后对每个调用 render 并返回结果。

    【讨论】:

      【解决方案3】:

      您指定的要求是使用正则表达式:

      1. 将文本分成组件(单词、...)
      2. 返回带有内部算术表达式的文本

      您已经开始使用正则表达式的第一步,但还没有完全完成——完成后,还有:

      1. 识别和解析构成算术(子)表达式的组件。
      2. 评估已识别的(子)表达式组件并生成一个值。对于以中缀表示法计算(子)表达式,有exists a very helpful answer
      3. 将值替换替换回原始字符串——应该很简单。

      为了将文本划分为定义得足够严格以允许以后对子表达式进行明确评估的组件,我编写了一个示例,尝试了named capturing groups in Java。此示例仅处理整数,但浮点应该易于添加。

      一些测试输入的示例输出如下:

      Matching 'Pete like mathematic 5+3 and jesica too sin(3).'
      WORD('Pete'),WS(' '),WORD('like'),WS(' '),WORD('mathematic'),WS(' '),NUM('5'),OP('+'),NUM('3'),WS(' '),WORD('and'),WS(' '),WORD('jesica'),WS(' '),WORD('too'),WS(' '),FUNC('sin'),FOPENP('('),NUM('3'),CLOSEP(')'),DOT('.')
      Matching 'How about solving sin(3 + cos(x)).'
      WORD('How'),WS(' '),WORD('about'),WS(' '),WORD('solving'),WS(' '),FUNC('sin'),FOPENP('('),NUM('3'),WS(' '),OP('+'),WS(' '),FUNC('cos'),FOPENP('('),WORD('x'),CLOSEP(')'),CLOSEP(')'),DOT('.')
      Matching 'Or arcsin(4.2) we do not know about?'
      WORD('Or'),WS(' '),WORD('arcsin'),OPENP('('),NUM('4'),DOT('.'),NUM('2'),CLOSEP(')'),WS(' '),WORD('we'),WS(' '),WORD('do'),WS(' '),WORD('not'),WS(' '),WORD('know'),WS(' '),WORD('about'),PUNCT('?')
      Matching ''sin sin sin' the catholic priest has said...'
      PUNCT('''),WORD('sin'),WS(' '),WORD('sin'),WS(' '),WORD('sin'),PUNCT('''),WS(' '),WORD('the'),WS(' '),WORD('catholic'),WS(' '),WORD('priest'),WS(' '),WORD('has'),WS(' '),WORD('said'),DOT('.'),DOT('.'),DOT('.')
      

      关于命名捕获组的使用,我发现编译Pattern 或获取Matcher API 不提供对当前组名的访问很不方便。示例代码如下。

      import java.util.*;
      import java.util.regex.*;
      
      import static java.util.stream.Collectors.joining;
      
      public class Lexer {
          // differentiating _function call opening parentheses_ from expressions one
          static final String S_FOPENP = "(?<fopenp>\\()";
          static final String S_FUNC = "(?<func>(sin|cos|tan))" + S_FOPENP;
          // expression or text opening parentheses
          static final String S_OPENP = "(?<openp>\\()";
          // expression or text closing parentheses
          static final String S_CLOSEP = "(?<closep>\\))";
          // separate dot, should help with introducing floating-point support
          static final String S_DOT = "(?<dot>\\.)";
          // other recognized punctuation
          static final String S_PUNCT = "(?<punct>[,!?;:'\"])";
          // whitespace
          static final String S_WS = "(?<ws>\\s+)";
          // integer number pattern
          static final String S_NUM = "(?<num>\\d+)";
          // treat '* / + -' as mathematical operators. Can be in dashed text.
          static final String S_OP = "(?<op>\\*|/|\\+|-)";
          // word -- refrain from using \w character class that also includes digits
          static final String S_WORD = "(?<word>[a-zA-Z]+)";
      
          // put the predefined components together into single regular expression
          private static final String S_ALL = "(" +
              S_OPENP + "|" + S_CLOSEP + "|" + S_FUNC + "|" + S_DOT + "|" +
              S_PUNCT + "|" + S_WS + "|" + S_NUM + "|" + S_OP + "|" + S_WORD +
          ")";
          static final Pattern ALL = Pattern.compile(S_ALL); // ... & form Pattern
      
          // named capturing groups defined in regular expressions
          static final List<String> GROUPS = Arrays.asList(
              "func", "fopenp",
              "openp", "closep",
              "dot", "punct", "ws",
              "num", "op",
              "word"
          );
          // divide match into components according to capturing groups
          static final List<String> tokenize(Matcher m) {
              List<String> tokens = new LinkedList<>();
              while (m.find()){
                  for (String group : GROUPS) {
                      String grResult = m.group(group);
                      if (grResult != null)
                          tokens.add(group.toUpperCase() + "('" + grResult + "')");
                  }
              }
      
              return tokens;
          }
      
          // some sample inputs to test
          static final List<String> INPUTS = Arrays.asList(
              "Pete like mathematic 5+3 and jesica too sin(3).",
              "How about solving sin(3 + cos(x)).",
              "Or arcsin(4.2) we do not know about?",
              "'sin sin sin' the catholic priest has said..."
          );
      
          // test
          public static void main(String[] args) {
              for (String input: INPUTS) {
                  Matcher m = ALL.matcher(input);
                  System.out.println("Matching '" + input + "'");
                  System.out.println(tokenize(m).stream().collect(joining(",")));
              }
          }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-12-12
        • 1970-01-01
        • 1970-01-01
        • 2013-10-27
        • 2017-09-27
        相关资源
        最近更新 更多