Scala:有更好的方法来评估表达式树吗?

时间:2015-06-07 20:08:51

标签: scala tree operators expression case-class

我正在通过“Scala for the Impatient”一书中的练习来学习Scala。一个问题是:

/**
   * Q8: Extends the tree in the preceding exercise so that each non-leaf node stores an operator in addition to
   * the child nodes. Then write a function `eval` that computes the value. For example, the tree
   *
   *         +
   *    *    2    -
   *  3   8      5
   *
   * has a value (3 * 8) + 2 + (-5) = 21
   *
   */

我的解决方案如下。你能改进吗?特别是,我想知道是否有一种方法可以直接匹配函数而不是方法名称。例如,如果我可以编写类似下面的虚构语句

case ExprTreeNode(f, children @ _*) if (f == Int.+ || f == Int.-) => children.foldLeft(0) { (acc, elem) => eval(elem) f acc }

然后我可以合并+-个案。 */同样如此。

sealed abstract class ExpressionTree
case class ExprTreeLeaf(value: Int) extends ExpressionTree
case class ExprTreeNode(op: String, children: ExpressionTree*) extends ExpressionTree

def eval(expr: ExpressionTree): Int = expr match {
  case ExprTreeNode("+", children @ _*) => children.foldLeft(0) { _ + eval(_) }
  case ExprTreeNode("*", children @ _*) => children.foldLeft(1) { _ * eval(_) }
  case ExprTreeNode("/", children @ _*) => children.foldLeft(1) { (acc, elem) => eval(elem) / acc }
  case ExprTreeNode("-", child) => eval(child).unary_- // Order matters here, 'children @ _*' would match 1 child
  case ExprTreeNode("-", children @ _*) => children.foldLeft(0) { (acc, elem) => eval(elem) - acc }
  case leaf: ExprTreeLeaf => leaf.value
}

测试用例:

"Method eval" should "evaluate an expression tree" in {
  val expr = ExprTreeNode("+", ExprTreeNode("*", ExprTreeLeaf(3), ExprTreeLeaf(8)), ExprTreeLeaf(2), ExprTreeNode("-", ExprTreeLeaf(5)))

  eval(expr) should be(21)
}

3 个答案:

答案 0 :(得分:6)

一些想法:

object TestApp extends App{

  sealed abstract class Tree
  case class Leaf(value: Int) extends Tree
  case class Node(op: String, children: Tree*) extends Tree



  //change to map and reduce to remove need for initial value
  def evalReduce(expr: Tree): Int = expr match {
    case Node("-", child) => evalReduce(child).unary_- // Order matters here, 'children @ _*' would match 1 child
    case Node("+", children @ _*) => children.map(evalReduce).reduceLeft(_+_)
    case Node("*", children @ _*) => children.map(evalReduce).reduceLeft(_*_)
    case Node("/", children @ _*) => children.map(evalReduce).reduceLeft(_/_)
    case Node("-", children @ _*) => children.map(evalReduce).reduceLeft(_-_)
    case leaf: Leaf => leaf.value
  }

  // long to declare plus/minus/divide/multiply functions
  // not much prettier/simpler than evalReduce
  val stringToFunction = Map[String,(Int,Int)=>Int](
    "+"->{(i:Int,j:Int)=>i+j},
    "*"->{(i:Int,j:Int)=>i*j},
    "/"->{(i:Int,j:Int)=>i/j},
    "-"->{(i:Int,j:Int)=>i-j}
  )

  def evalCasesUnified(expr: Tree): Int = expr match {
    case Node("-", child) => evalCasesUnified(child).unary_- // Order matters here, 'children @ _*' would match 1 child
    case Node(s, children @ _*) => children.map(evalCasesUnified).reduceLeft(stringToFunction(s))
    case leaf: Leaf => leaf.value
  }


  sealed abstract class TreeFunctional
  case class LeafFunctional(value: Int) extends TreeFunctional
  case class NodeUnaryFunctional(op: Int=>Int, child: TreeFunctional) extends TreeFunctional
  case class NodeFunctional(op: (Int,Int)=>Int, children: TreeFunctional*) extends TreeFunctional

  def evalFunctional(expr: TreeFunctional): Int = expr match {
    case NodeUnaryFunctional(f, child) => f(evalFunctional(child)) 
    case NodeFunctional(f, children @ _*) => children.map(evalFunctional).reduceLeft(f)
    case leaf: LeafFunctional => leaf.value
  }
  val expr = Node("+", Node("*", Leaf(3), Leaf(8)), Leaf(2), Node("-", Leaf(5)))
  val exprFunctional = NodeFunctional({_+_}, NodeFunctional({_*_}, LeafFunctional(3), LeafFunctional(8)), LeafFunctional(2), NodeUnaryFunctional({-_}, LeafFunctional(5)))

  def plus(i:Int,j:Int):Int = {i+j}
  def minus(i:Int,j:Int):Int = {i-j}
  def minusUnary(i:Int):Int = -i
  def multiply(i:Int,j:Int):Int = {i*j}
  def divide(i:Int,j:Int):Int = {i/j}

  val exprFunctionalNicer = NodeFunctional(plus, NodeFunctional(multiply, LeafFunctional(3), LeafFunctional(8)), LeafFunctional(2), NodeUnaryFunctional(minusUnary, LeafFunctional(5)))

  //but then you could create a case class for each function

  sealed abstract class TreeNamed
  case class Value(value: Int) extends TreeNamed

  abstract class UnaryNode() extends TreeNamed {
    val child: TreeNamed
    def op: Int=>Int
  }
  case class MinusUnary(child:TreeNamed) extends UnaryNode{
    def op = {-_}
  }

  abstract class MultinaryNode() extends TreeNamed {
    val children: Seq[TreeNamed]
    def op: (Int,Int)=>Int
  }

  case class Plus(children:TreeNamed*) extends MultinaryNode{
    def op = {_+_}
  }
  case class Minus(children:TreeNamed*) extends MultinaryNode{
    def op = {_-_}
  }
  case class Multiply(children:TreeNamed*) extends MultinaryNode{
    def op = {_*_}
  }
  case class Divide(children:TreeNamed*) extends MultinaryNode{
    def op = {_/_}
  }

  val exprNamed = Plus(Multiply(Value(3), Value(8)), Value(2), MinusUnary(Value(5)))

  def evalNamed(expr: TreeNamed): Int = expr match {
    case u:UnaryNode => u.op(evalNamed(u.child))
    case m:MultinaryNode => m.children.map(evalNamed).reduceLeft(m.op)
    case leaf: Value => leaf.value
  }


  println(evalReduce(expr))
  println(evalCasesUnified(expr))
  println(evalFunctional(exprFunctional))
  println(evalFunctional(exprFunctionalNicer))
  println(evalNamed(exprNamed))
}

答案 1 :(得分:1)

您可以将op定义为List[Int]Int的功能。这样,eval将返回值,以防树是叶子,否则op(children map eval)。请注意,您可能希望将toString作为构造函数参数传递给ExprTreeNode,因为函数没有很好的字符串表示形式。

此解决方案如下所示:

sealed abstract class ExpressionTree

case class ExprTreeLeaf(value: Int) extends ExpressionTree

case class ExprTreeNode(op: List[Int] => Int, children: List[ExpressionTree]) extends ExpressionTree

object ExprTreeNode {
  // convenience method
  def apply(op: List[Int] => Int, children: ExpressionTree*) = new ExprTreeNode(op, children.toList)
}

object ExpressionTree {

  val Plus: List[Int] => Int = a => a.sum
  val UnaryMinus: List[Int] => Int = {
    case List(a: Int) => -a
  }
  val Times: List[Int] => Int = a => a.product

  def main(args: Array[String]) {
    val expr = ExprTreeNode(Plus, ExprTreeNode(Times, ExprTreeLeaf(3), ExprTreeLeaf(8)), ExprTreeLeaf(2), ExprTreeNode(UnaryMinus, ExprTreeLeaf(5)))
    println(eval(expr))
  }

  def eval(expr: ExpressionTree): Int = expr match {
    case ExprTreeLeaf(value) => value
    case ExprTreeNode(op, children) => op(children map eval)
  }
}

但是有一个问题:您没有静态保证函数op的参数与children的长度相同。为了获得静态安全性,您可以只允许一元树和二元树(分别使用op: (Int, Int) => Intop: Int => Int),使用无形,特别是Sized,它编码列表的长度(如果静态知道的类型系统。

答案 2 :(得分:0)

可能会简化 eval 功能

我使用自定义类型( ExprOp )来表示操作(+, - ,*等) 此代码不完整,但足以传达 ExprOp 可能有用的想法:)

//here is the expression tree :
trait Expr
case class Value(value:Int) extends Expr
case class Op(op : ExprOp, left : Expr, right : Expr) extends Expr

//here are the operations :
trait ExprOp{
    def apply(lhs:Int, rhs:Int) : Int
}
object Add extends ExprOp{
    def apply(lhs:Int, rhs:Int) : Int = lhs + rhs
}
object Mul extends ExprOp{
    def apply(lhs:Int, rhs:Int) : Int = lhs * rhs
} //... Create object Div, Sub in the same way

// here is the eval function 
// notice how simple it is ! 

def eval(e:Expr):Int = e match {
    case Value(n) => n
    case Op(op, lhs, rhs) => op(eval(lhs), eval(rhs))
}  //nothing more needed