OO第一次作业实现细节与设计思路

转自我的个人博客,原文地址http://mistariano.com/blog/12

只是我个人的一些实现思路,抛砖引玉~

多项式类 PolyExp/PolyTerm

设计PolyExpPolyTerm两个类对多项式抽象。一个多项式是若干项的加和,因此很容易想到多项式对象has-a一组项。
考虑到同类项合并的天然性质,用Map维护多项式的项,进一步考虑数据范围约束(有效输入不长于1000),选用HashMap

PolyTerm实现一个add方法用于合并同类项,PolyExp也实现一个add方法,实现Exp与Exp、Exp与Term间的加法。一个新项加入多项式后立即检查并合并同类项

重载PolyTermtoString,可以返回该项的最短合法形式(若为0则返回空串)
重载PolyExptoString,遍历所有Term并将其toString加和,注意这里如果加和是空串,说明所有Term的语义都是“0”,因此要特判并返回"0";对于第一个字符是'+'的情况,扔掉加号来优化字符串长度

这里有一个小trick,为了尽量让首位是正号,在遍历Term前先依照其系数降序对其排序。因此实现了PolyTerm类的compareTo方法。排序还有一个好处是,如果排序结果是确定的(这意味着我们需要先排系数,系数相等时还要排指数),可以直接根据两个PolyExptoString返回值判断两个PolyExp是否相等,便于设计单元测试。因此还额外实现了两个类的equalTo

求导的实现其实是很简单的,毕竟只需要符号求导。Exp求导方法的返回值也是Exp对象,这样可以复用Exp类的各种方法,合情合理。

其实多项式类和求导不是这次的难点,难点是处理输入。

处理魔鬼输入:PolyPattern 和 PolyMultiMatcher

拆解一下输入的需求:

我们有如下类型的语素:

  1. 加号+或减号-
  2. 乘号*
  3. 上标^
  4. 符号整数[+-]?[0-9]+

一个字符串是合法输入的必要条件:它是任意数量空格和/或制表符连接若干语素得到的串

进一步地,我们继续分割得到如下几个类型的语素:

  1. 连接多项式项的加号+或减号-
  2. 代表系数1或-1的加号+或减号-
  3. 乘号*
  4. 上标^
  5. 代表系数的符号整数
  6. 代表指数的符号整数

我们分别用

  • S 表示 一个连接多项式项的加号+或减号-([+-])
  • + 表示 代表系数1或-1的加号+或减号-,允许缺失([+-]?)
  • * 表示 乘号*(\*
  • ^ 表示 上标^(^
  • c 表示 代表系数的符号整数([+-]?[0-9]+)
  • i 表示 代表指数的符号整数([+-]?[0-9]+)

那么一个合法的包含前面加号或减号的表达式项,应当是如下七种情况之一:

  • Sc
  • Sx
  • Sc*x
  • S+x
  • Sx^i
  • Sc*x^i
  • S+x^i

PolyPattern是这样一个类:我输入一个形如c*x^i的字符串,它能自动将其转换为对应的正则表达式,并可以根据getCoefgetIndex两个方法提取ci对应的group。这样搞我个人觉得有两个好处:

  1. 更可读
  2. 更易扩展。比如某天要写个对数函数加和求导

PolyMultiMathcer则是同时管理多个PolyPattern,对于一个给定的字符串,每次用所有Pattern尝试提取其最前面的一个合法表达式项,从所有提取结果中,贪心地选择最长的结果作为当前的匹配结果。注意由于只需提取字符串开头的子串,因此进一步用^锚点优化PolyPattern生成的正则表达式

迭代这个过程,若一个字符串不能完成匹配,则其必定不是合法输入,反之则可认为提取成功。这个提取逻辑封装为PolyParser类,单例模式。

总结

这次作业是一次很好的练习,一方面需要最基本的OO抽象能力,另一方面也让我对正则有了更深的理解

设计上的弯路还是走了很多的,比如在实现目前的PolyPattern前试过一个大正则表达式和一个状态机,这三种设计的优劣在我上一篇博客里简要分析了

以及PolyTermPolyExp都实现了一个可导接口,这个设计其实是不必要的。大概是看见自动求导就想起刚读完的Keras源码了吧23333 深受毒害

期待接下来的Tasks。

附上代码

package homework.poly.parse.matcher;

import homework.poly.PolyExp;
import jdk.nashorn.internal.runtime.regexp.joni.exception.ValueException;

import java.math.BigInteger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class PolyPattern {
    private static String BIG_INT_PATTERN = "([-+]?[0-9]+)";
    private static String PM_PATTERN = "([-+])";
    private static String COEF_PM_PATTERN = "([-+])?";
    private static String BLANK_PATTERN = "[\\ \\t]*";
    private final int signGroupInd = 1;
    private String rePatternStr;
    private String polyPatternStr;
    private Pattern rePattern;
    private Matcher matcher;
    private int coefGroupInd = 0;
    private int indexGroupInd = 0;
    private BigInteger defaultIndex = BigInteger.ONE;
    private BigInteger defaultCoef = BigInteger.ONE;

    private PolyPattern(String polyPatternStr, boolean setDefaultIndexToZero) {
        this.polyPatternStr = polyPatternStr;
        rePatternStr = buildRePatternStr();
        rePattern = Pattern.compile(rePatternStr);
        if (setDefaultIndexToZero) {
            defaultIndex = BigInteger.ZERO;
        }
    }

    /**
     * compile a matcher by a polyPatternStr String.
     * <p>
     * reg exp can be compiled automatically
     *
     * @param polyPatternStr        a polyPatternStr string.
     *                              a polyPatternStr should be a string contains
     *                              only [ c | + | * | x | ^ | i ]
     *                              <p>
     *                              where
     *                              <p>
     *                              - c is a number placeholder, for coef
     *                              <p>
     *                              - + is a sign placeholder, for +/- before x
     *                              (e.g. -x, + x, - x^2)
     *                              <p>
     *                              - i is a number placeholder, for index
     * @param setDefaultIndexToZero set the default index value from 1 to 0
     */
    public static PolyPattern compile(String polyPatternStr,
                                      boolean setDefaultIndexToZero) {
        String striped = polyPatternStr.replaceAll("\\s", "");
        Pattern checkPattern = Pattern.compile("^[c+*x^i]+$");
        if (!checkPattern.matcher(striped).find()) {
            throw new ValueException("Bad poly pattern str: " + polyPatternStr);
        }
        return new PolyPattern(polyPatternStr, setDefaultIndexToZero);
    }

    public String getPolyPatternStr() {
        return polyPatternStr;
    }

    private String buildRePatternStr() {
        StringBuilder builder = new StringBuilder();
        builder.append("^");
        builder.append(BLANK_PATTERN);
        builder.append(PM_PATTERN);
        builder.append(BLANK_PATTERN);
        int groupCount = 2;
        for (char c :
            getPolyPatternStr().toCharArray()) {
            switch (c) {
                case 'c': {
                    builder.append(BIG_INT_PATTERN);
                    coefGroupInd = groupCount;
                    groupCount += 1;
                    break;
                }
                case 'i': {
                    builder.append(BIG_INT_PATTERN);
                    indexGroupInd = groupCount;
                    groupCount += 1;
                    break;
                }
                case '+': {
                    builder.append(COEF_PM_PATTERN);
                    coefGroupInd = groupCount;
                    groupCount += 1;
                    break;
                }
                case 'x': {
                    builder.append("x");
                    break;
                }
                case '^': {
                    builder.append("\\^");
                    break;
                }
                case '*': {
                    builder.append("\\*");
                    break;
                }
                default: {
                    throw new ValueException("Unexpected char \'" + c + "\'");
                }
            }
            builder.append(BLANK_PATTERN);
        }
        return builder.toString();
    }

    Matcher matcher(String str) {
        matcher = rePattern.matcher(str);
        return matcher;
    }

    boolean find() {
        return matcher.find();
    }

    private BigInteger getSign() {
        if (matcher.group(signGroupInd) == null) {
            return BigInteger.ONE;
        }
        if (matcher.group(signGroupInd).equals("-")) {
            return BigInteger.ONE.negate();
        } else {
            return BigInteger.ONE;
        }
    }

    private BigInteger getCoef() {
        if (coefGroupInd == 0) {
            return defaultCoef;
        }
        String coefStr = matcher.group(coefGroupInd);
        if (coefStr == null) {
            return BigInteger.ONE;
        } else if (coefStr.equals("-")) {
            return BigInteger.ONE.negate();
        } else if (coefStr.equals("+")) {
            return BigInteger.ONE;
        }
        return new BigInteger(coefStr);
    }

    private BigInteger getIndex() {
        if (indexGroupInd == 0) {
            return defaultIndex;
        }
        return new BigInteger(matcher.group(indexGroupInd));
    }

    PolyExp getTermExp() {
        return new PolyExp(getIndex(), getCoef().multiply(getSign()));
    }

    String getMatchedString() {
        return matcher.group(0);
    }

    @Override
    public String toString() {
        return "PolyPattern{" +
            "\'" + polyPatternStr + '\'' +
            '}';
    }
}
package homework.poly.parse.matcher;

import homework.poly.PolyExp;

import java.util.LinkedList;

public class PolyMultiMatcher {
    private LinkedList<PolyPattern> patterns = new LinkedList<>();
    private PolyPattern cachedPattern;

    private PolyMultiMatcher() {
        addMatcher("c*x^i");
        addMatcher("+x^i");
        addMatcher("x^i");
        addMatcher("c*x");
        addMatcher("+x");
        addMatcher("x");
        addMatcher("c", true);
    }

    public static PolyMultiMatcher getInstance() {
        return PolyMatcherMgrSingleton.singleton;
    }

    private void addMatcher(String polyPattern, boolean isConstant) {
        PolyPattern matcher = PolyPattern.compile(polyPattern, isConstant);
        patterns.add(matcher);
    }

    private void addMatcher(String polyPattern) {
        addMatcher(polyPattern, false);
    }

    public void matchAll(String str, boolean debug) {
        for (PolyPattern pattern :
            patterns) {
            pattern.matcher(str);
        }
        if (debug) {
            System.out.println("===========================");
            System.out.println("Matching " + str);
        }
    }

    public boolean findAny(boolean debug) {
        boolean res = false;
        PolyPattern currentBestPtn = null;
        int maxLen = 0;
        for (PolyPattern matcher :
            patterns) {
            boolean find = matcher.find();
            if (debug) {
                System.out.println("matcher = " + matcher);
                System.out.println("find = " + find);
            }
            if (find) {
                res = true;
                if (matcher.getMatchedString().length() > maxLen) {
                    currentBestPtn = matcher;
                    maxLen = matcher.getMatchedString().length();
                }
            }
        }
        cachedPattern = currentBestPtn;
        if (debug) {
            System.out.println("cachedPattern = " + cachedPattern);
        }
        return res;
    }

    public PolyExp getTermExp() {
        return cachedPattern.getTermExp();
    }

    public int getMatchedLength() {
        return cachedPattern.getMatchedString().length();
    }

    private static class PolyMatcherMgrSingleton {
        private static final PolyMultiMatcher singleton =
            new PolyMultiMatcher();
    }
}
package homework.poly.parse;

import homework.poly.PolyExp;
import homework.poly.parse.matcher.PolyMultiMatcher;

import java.util.regex.Pattern;

public class PolyParser {

    /**
     * Default constructor
     */
    private PolyParser() {
    }

    public static PolyParser getInstance() {
        return PolyParserSingleton.singleton;
    }

    /**
     * Parse a PolyExp from giving string
     *
     * @param input just a string
     * @return parsed expression. if failed, return null
     */
    public PolyExp parseExp(String input) {
        return parseExp(input, false);
    }

    public PolyExp parseExp(String input, boolean debug) {
        String buffer = preprocessInput(input);
        PolyMultiMatcher matcher = PolyMultiMatcher.getInstance();
        PolyExp resExp = new PolyExp();
        while (!buffer.isEmpty()) {
            matcher.matchAll(buffer, debug);
            if (matcher.findAny(debug)) {
                resExp.add(matcher.getTermExp());
                buffer = buffer.substring(matcher.getMatchedLength());
            } else {
                return null;
            }
        }
        return resExp;
    }

    /**
     * Check & add a '+' at the head of giving string.
     *
     * @param input just input
     * @return a string whose first visible character is '+' or '-'
     */
    private String preprocessInput(String input) {
        Pattern headSignPattern = Pattern.compile("^\\s*[-+]");
        if (!headSignPattern.matcher(input).find()) {
            return "+" + input;
        } else {
            return input;
        }
    }

    /**
     * Singleton keeper class
     */
    private static class PolyParserSingleton {
        private static final PolyParser singleton = new PolyParser();
    }

}
package homework.poly;

import java.math.BigInteger;
import java.util.Objects;

/**
 * Poly Term.
 * <p>
 * Contains an index and a coefficient
 */
public class PolyTerm implements Derivable {
    private final BigInteger index;
    private BigInteger coef;

    PolyTerm(BigInteger ind, BigInteger coe) {
        index = ind;
        coef = coe;
    }

    PolyTerm(int ind, int coe) {
        this(new BigInteger(String.valueOf(ind)),
            new BigInteger(String.valueOf(coe)));
    }

    static PolyTerm constant(BigInteger coe) {
        return new PolyTerm(BigInteger.ZERO, coe);
    }

    BigInteger getIndex() {
        return index;
    }

    private BigInteger getCoef() {
        return coef;
    }

    PolyTerm add(PolyTerm other) {
        assert (other.getIndex().equals(index));
        coef = coef.add(other.getCoef());
        return this;
    }

    void negate() {
        coef = coef.negate();
    }

    @Override
    public PolyTerm getDerivative() {
        if (index.equals(BigInteger.ZERO)) {
            return new PolyTerm(BigInteger.ZERO, BigInteger.ZERO);
        } else {
            return new PolyTerm(
                index.subtract(BigInteger.ONE), index.multiply(coef));
        }
    }

    /**
     * format as coef*x^index
     * <p>
     * return "" if coef = 0
     * <p>
     * return x^index if coef = 1
     * <p>
     * return -x^index if coef = -1
     * <p>
     * return coef as a constant if index = 0
     * <p>
     * return coef*x if index = 1
     *
     * @return formatted string
     */
    @Override
    public String toString() {

        // zero
        if (BigInteger.ZERO.equals(coef)) {
            return "";
        }

        String coefStr = coef.toString();
        if (coef.compareTo(BigInteger.ZERO) > 0) {
            coefStr = "+" + coefStr;
        }

        if (BigInteger.ZERO.equals(index)) {
            // constant
            return coefStr;
        } else {
            coefStr = coefStr + "*";
            if (coef.equals(BigInteger.ONE)) {
                coefStr = "+";
            } else if (coef.equals(BigInteger.ONE.negate())) {
                coefStr = "-";
            }
            if (BigInteger.ONE.equals(index)) {
                return coefStr + "x";
            } else {
                return coefStr + "x" + "^" + index.toString();
            }
        }
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        PolyTerm polyTerm = (PolyTerm) o;
        return Objects.equals(index, polyTerm.index) &&
            Objects.equals(coef, polyTerm.coef);
    }

    /**
     * greater coef first
     * <p>
     * if coef equals, less index first
     *
     * @param other the other one
     * @return -1 if less than, 1 if greater than, 0 if equal
     */
    int compareTo(PolyTerm other) {
        if (coef.compareTo(other.getCoef()) != 0) {
            return -coef.compareTo(other.getCoef());
        } else {
            return index.compareTo(other.getCoef());
        }
    }
}
package homework.poly;

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Objects;

/**
 * Poly Expression.
 * <p>
 * contains a sort of poly terms
 */
public class PolyExp implements Derivable {

    private HashMap<BigInteger, PolyTerm> terms;

    public PolyExp() {
        this(16);
    }

    public PolyExp(int initialCapacity) {
        terms = new HashMap<>(initialCapacity);
    }


    /**
     * Shouldn't use this one to create a new PolyExp
     * <p>
     * you should use PolyExp(int, int) or
     * PolyExp(BigInteger, BigInteger) instead
     *
     * @param term initial PolyTerm
     */
    PolyExp(PolyTerm term) {
        this();
        terms.put(term.getIndex(), term);
    }

    public PolyExp(BigInteger index, BigInteger coef) {
        this(new PolyTerm(index, coef));
    }

    public PolyExp(int index, int coef) {
        this(new PolyTerm(index, coef));
    }

    public PolyExp add(PolyExp other) {
        other.getTerms().forEach((k, v) -> terms.merge(k, v, PolyTerm::add));
        return this;
    }

    PolyExp add(PolyTerm term) {
        BigInteger index = term.getIndex();
        if (terms.containsKey(index)) {
            PolyTerm termExisted = terms.get(index);
            PolyTerm termAdded = termExisted.add(term);
            terms.replace(index, termAdded);
        } else {
            terms.put(index, term);
        }
        return this;
    }

    private HashMap<BigInteger, PolyTerm> getTerms() {
        return terms;
    }

    @Override
    public PolyExp getDerivative() {
        PolyExp derExp = new PolyExp();
        terms.forEach((k, v) -> derExp.add(v.getDerivative()));
        return derExp;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        PolyExp exp = (PolyExp) o;
        return toSortedString().equals(exp.toSortedString());
    }


    /**
     * to sorted string
     *
     * @return sorted string
     */
    @Override
    public String toString() {
        return toSortedString();
    }

    private String toSortedString() {
        StringBuilder builder = new StringBuilder();
        ArrayList<PolyTerm> list = new ArrayList<>(terms.values());
        list.sort(PolyTerm::compareTo);
        list.forEach(builder::append);
        String res = builder.toString();
        if (res.isEmpty()) {
            res = "0";
        }
        if (res.charAt(0) == '+') {
            res = res.substring(1);
        }
        return res;
    }

    @Override
    public int hashCode() {
        return Objects.hash(terms);
    }
}

猜你喜欢

转载自www.cnblogs.com/MisTariano/p/10494322.html