Formatting an arithmetic expression program
To practice the use of pattern matching, this blog post introduces programs that write formatting arithmetic expressions, and the final rendering results are shown in the mathematical expression of the two-dimensional layout below, where the division operation is printed vertically:
1 - * (x + 1)2 ----------- x 1.5 - + --- 2 x
In order to implement this program, we need to do a bit of work:
1. 编写一个二维布局库来创建和渲染二维布局元素。这里主要应用Scala面向对象编程的一些方法,通过组合与继承来构建简单部件,进而实现库的设计。2. 编写一个表达式的格式化类,利用二维布局库来渲染二维字符图形,从而实现一个完整的呈现效果。
Two-dimensional layout library
In a two-dimensional layout library, we define classes so that element objects can be constructed from simple parts, including arrays, rows, and rectangles. We will also define the combined operators above
and beside
. This combination operator can synthesize elements of certain areas into new elements.
Implement above and beside
The above method in the element class places one element on top of another, and is actually the value of the contents that connects the two elements. ++
connect two arrays using an operator. We also want to consider the width of the two elements here.
The beside method, which puts two elements together, creates a new element, and each row of the new element comes in tandem with the two primitive elements. Here also consider the different heights of the two elements.
Implement widen and heighten
The widen method is called by above to ensure that the element is stacked together with the same width, and the heighten method is beside called to ensure that the elements that are joined together have the same height.
Defining Factory objects
The Factory object contains methods for building other objects. Customers can construct objects by using the factory method instead of directly using new. The advantage of this approach is that you can centralize the creation of objects and hide the details of the classes that the objects actually represent.
Here we create the associated object of the element class and use it as the factory method for the layout element. In this way, the only thing you expose to the client is the combination of the class/object of element, which hides the implementations of the Arrayelement,lineelement and uniformelement three classes.
Three Elem factory methods to create different classes, where parameters come and go to differentiate different classes.
Complete program
Packagecom.jason.exprImportElement.elemAbstract class Element { defContents:array[string]defWidth:int = Contents (0). lengthdefHeight:int = Contents.lengthdefAbove (that:element): Element = {ValThis1 = ThisWiden That.widthValTHAT1 = that widen This. width elem (this1.contents + that1.contents)}defBeside (that:element): Element = {ValThis1 = ThisHeighten That.heightValTHAT1 = that heighten This. Height Elem ( for((line1, line2) <-this1.contents zip that1.contents)yieldLINE1+LINE2)}defWiden (w:int): Element = {if(w <= width) This Else{Valleft = Elem ("', (W-width)/2, height)Valright = Elem ("', w-width-left.width, height) left beside ThisBeside right}}defHeighten (h:int): Element = {if(H <= height) This Else{Valtop = Elem ("', width, (h-height)/2)Valbot = Elem ("', width, h-height-top.height) top above ThisAbove bot}}Override deftoString = Contents mkstring"\ n"} Object Element { Private class arrayelement(Val contents:array[string]) extends Element Private class lineelement(s:string) extends Element { ValContents = Array (s)Override defwidth = s.lengthOverride defHeight =1}Private class uniformelement(Ch:char, Overri De Val width:int, override Val height:int) extends Element { Private ValLine = ch.tostring * WidthdefContents = Array.fill (height) {line}}defElem (contents:array[string]): Element =NewArrayelement (contents)defElem (Chr:char, Width:int, height:int): Element =NewUniformelement (CHR, width, height)defElem (line:string): Element =NewLineelement (line)}
Formatting expression class operator precedence
First, on a horizontal layout, a structured expression, such as:
BinOp("+", BinOp("+", BinOp("+", Var("x"), Var("y")), Var("z")), Number(1))
should be printed as (x+y)*z+1
. Note that the parentheses on both sides of the x+y are mandatory, but (x+y)*z
both sides are optional. To ensure the best readability of the layout, the goal is to remove the redundant parentheses and keep all the parentheses that must exist.
in operator precedence, you can take an operator group that defines only the increment priority, and then calculate the priority of each operator by calculating the precedence level. This saves a lot of pre-calculations on the priority level.
precedence
Variables are mappings from operators to their precedence, integers starting at 0. It is calculated from a for expression with two generators. The first generator produces opGroups
each index of the array i
. The second generator produces each of the opGroups(i)
operators op
. The for expression creates an association from the op
operator to its index for each of these operators i
. Therefore, the relative position of the operator in the array is taken as its priority.
The handling of operator precedence is this:
ValOpgroups =array (Set ("|","||"), Set ("&","&&"), Set ("^"), Set ("==","!="), Set ("<","<=",">",">="), Set ("+","-"), Set ("*","%"))ValPrecedence = {ValAssocs = for{i <-0Until Opgroups.length op <-opgroups (i)}yieldOp-I Map () + + Assocs}
Now you also need to consider the precedence of the two operators, which are the precedence of the unary and division operators, respectively. The precedence of unary operators is higher than any binary operator, whose priority is 1 greater than the priority of * and%, set unaryPrecedence
to the length of opgroups, and the priority of the division operator fractionPrecedence
is-1, in which case additional processing and manipulation is performed.
Pattern matching in a format operation
The Main method format is used to produce a layout element that expresses a two-dimensional array of characters that accept an expression and the operator precedence of an expression. The Format method accomplishes its work by performing pattern matching on the type of the expression.
Let's focus on the sample of the unary operator and the two-dollar operator.
case UnOp(op, arg) => elem(op) beside format(arg, unaryPrecedence)
Explanation: Unary operations, the result is composed of the result of manipulating OP and the highest environment precedence format parameter arg.
case BinOp ( "/" , left, right ) = = {Val top = format (left, fractionprecedence) val bot = format (right , Fractionprecedence) Val line = Elem ( '-' , top.width max bot.width, 1 ) val frac = top above line above bot if (Enclprec! = fractionprecedence) frac else elem () beside Frac beside Elem ()}
Explanation: If the expression is a fraction, the intermediate result frac is placed vertically by the formatted operand left and right, separated by a horizontal line element. The width of the horizontal line is the maximum value of the formatted operand width.
Finally, add a space on each side of the frac to consider "(A/b)/C" If the result is:
a_b_c
This can be either "(A/b)/C" or "A/(B/C)". To eliminate ambiguous semantics, the embedded fractional A/b layout should have one space on each side. Became like this:
a _ b_ _ _ c
case BinOp (OP, Left, right ) = {val Opprec = Precedence (OP) val l = Format (left, Opprec) val r = format (right , Opprec + 1 ) val oper = l beside Elem ( + OP + ) Beside R if (Enclprec <= opprec) oper else elem ( "(" ) beside Oper beside Elem ()}
Explanation: For the normal two-dollar operator, it is first formatted with its operand left and right, and the priority of the formatted Zuo action element is the Opprec of the operator op, while the formatted rvalue is the priority plus 1. This design ensures that the brackets also reflect the correct union.
Like what:
BinOp("-", Var("a"), BinOp("-", Var("b"), Var("c")))
will be correctly divided into "a-(B-C)", and then the intermediate result oper by the formatted left and right operation of the element in turn, the middle plus operator composition. If the priority of the current operator is less than the precedence of the perimeter operator, then the Oper is placed in parentheses, otherwise it is returned as-is.
Complete program
Packagecom.jason.exprImportElement.elemSealed Abstract class Expr Case class Var(name:string) extends Expr Case class number(num:double) extends Expr Case class unop(operator:string, arg:expr) extends Expr Case class binop(operator:string, left:expr, right:expr) extends Expr class exprformatter { Private ValOpgroups = Array (Set ("|","||"), Set ("&","&&"), Set ("^"), Set ("==","!="), Set ("<","<=",">",">="), Set ("+","-"), Set ("*","%") )Private ValPrecedence = {ValAssocs = for{i <-0Until Opgroups.length op <-opgroups (i)}yieldOp-I Map () + + Assocs}Private ValUnaryprecedence = Opgroups.lengthPrivate ValFractionprecedence =-1 Private defFormat (e:expr, enclprec:int): Element = EMatch{ CaseVar (name) = Elem (name) CaseNumber (num) = {defStripdot (s:string) = {if(S EndsWith". 0") S.substring (0, S.length-2)Elses} elem (Stripdot (num.tostring))} CaseUnop (OP, arg) = Elem (OP) beside format (ARG, unaryprecedence) CaseBinop ("/", left, right) = {Valtop = Format (left, fractionprecedence)Valbot = Format (right, fractionprecedence)Valline = Elem ('-', Top.width Max Bot.width,1)ValFrac = top above line above botif(Enclprec! = fractionprecedence) fracElseElem" ") beside Frac beside Elem (" ") } CaseBinop (OP, left, right) = = {ValOpprec = Precedence (OP)ValL = Format (left, Opprec)ValR = Format (right, Opprec +1)Valoper = l beside Elem (" "+ OP +" ") beside Rif(Enclprec <= Opprec) operElseElem"(") beside Oper beside Elem (")") } }defFormat (e:expr): Element = Format (E,0)}
Test results
Packagecom.jason.expr Object Expression extendsApp{ Valf =NewExprformatterVale1 = Binop ("*", Binop ("/", Number (1), Number (2)), Binop ("+", Var ("x"), Number (1)))ValE2 = Binop ("+", Binop ("/", Var ("x"), Number (2)), Binop ("/", Number (1.5), Var ("x")))ValE3 = Binop ("/", E1, E2)defShow (e:expr) = println (F.format (E) +"\ n") for(e <-Array (E1, E2, E3)) Show (E)}
Final Print Effect:
1 - * (x + 1)2 x 1.5- + ---2 x 1 - * (x + 1)2 ----------- x 1.5 - + --- 2 x
reprint Please indicate the author Jason Ding and its provenance
Gitcafe Blog Home page (http://jasonding1354.gitcafe.io/)
GitHub Blog Home page (http://jasonding1354.github.io/)
CSDN Blog (http://blog.csdn.net/jasonding1354)
Jane Book homepage (http://www.jianshu.com/users/2bd9b48f6ea8/latest_articles)
Google search jasonding1354 go to my blog homepage
"Scala Programming" format arithmetic expression program