斯卡拉 : Is there a better way to evaluate an expression tree?

标签 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)
}

最佳答案

一些想法:

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))
}

关于斯卡拉 : Is there a better way to evaluate an expression tree?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30697946/

相关文章:

scala - 省略一些隐式参数

java - 如何在scala中使用flatMap来对一组 "vals"进行分组

c# - 平面数据到分层模型 C#

perl - =~ 在 Perl 中是什么意思?

perl - 为什么//=(定义或)对于数组的工作方式与对于标量的工作方式不同?

scala - 为什么AudioSystem.getMixerInfo()在sbt和Scala下返回不同的结果?

excel - flink InputStream 类 org.apache.commons.compress.archivers.zip.ZipFile$1 没有实现 InputStreamStatistics

C - 从二叉树中删除节点

c# - 是否有用于匹配(语法分析)树中模式的 C# 实用程序?

arrays - 为什么$ A + $ B和$ A,$ B与测试路径交互不同