Advertisement
roman_gemini

Arithmetic expression evaluator

Jul 10th, 2015
206
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Scala 6.02 KB | None | 0 0
  1. package org.gemini.scala.fun
  2.  
  3. /**
  4.  * Created by Roman on 08.07.2015
  5.  */
  6.  
  7. object Helper {
  8.   def isNumber(exp: String): Boolean =
  9.     exp.forall(c => c.isDigit || c == '.') && exp.count('.' ==) <= 1
  10.   def isWord(exp: String): Boolean = exp.forall(_.isLetter)
  11.   def isEmpty(exp: String): Boolean = exp.forall(_.isSpaceChar)
  12. }
  13.  
  14. object Calculator extends App {
  15.  
  16.   abstract class Token
  17.   abstract class Operation(val priority: Int) extends Token
  18.  
  19.   case class BinaryOperator(override val priority: Int, calc: (Double, Double) => Double) extends Operation(priority)
  20.   case class UnaryOperator(override val priority: Int, calc: Double => Double) extends Operation(priority)
  21.   case object ClosedBracket extends Token
  22.   case object OpenedBracket extends Token
  23.   case class Digit(value: Double) extends Token
  24.  
  25.   case class Output(values: List[Double] = Nil) {
  26.     def add(digit: Digit) = {
  27.       Output(values :+ digit.value)
  28.     }
  29.     def apply(op: Operation) = {
  30.       op match {
  31.         case b: BinaryOperator =>
  32.           if (values.length >= 2)
  33.             Output((values dropRight 2) :+ ((values takeRight 2) reduce b.calc))
  34.           else
  35.             throw new ArithmeticException("Not enough of operands")
  36.         case u: UnaryOperator =>
  37.           if (values.isEmpty) throw new ArithmeticException("Not enough of operands")
  38.           else Output(values.init :+ u.calc(values.last))
  39.         case _ =>
  40.           throw new UnsupportedOperationException
  41.       }
  42.     }
  43.     def result =
  44.       if (values.length != 1) throw new ArithmeticException("Syntax error")
  45.       else values.head
  46.   }
  47.  
  48.   case class Container(output: Output = new Output(), stack: List[Token] = Nil) {
  49.     def stackIsEmpty = stack.isEmpty
  50.     def lastIsOperation = stack.head.isInstanceOf[Operation]
  51.     def lastOperation = stack.head.asInstanceOf[Operation]
  52.     def result: Double = {
  53.       if (stack.contains(OpenedBracket))
  54.         throw new ArithmeticException("Opened bracket has no pair")
  55.       else
  56.         stack.collect({ case op: Operation => op }).foldLeft(output)(_ apply _).result
  57.     }
  58.   }
  59.  
  60.   import Helper._
  61.  
  62.   private def tokenize(list: List[Token], item: String): List[Token] = {
  63.     val res = item match {
  64.       case d if isNumber(d) => Digit(d.toDouble)
  65.       case "pi" => Digit(scala.math.Pi)
  66.       case "(" => OpenedBracket
  67.       case ")" => ClosedBracket
  68.       case "+" => BinaryOperator(0, _ + _)
  69.       case "-" if list.isEmpty || list.last == OpenedBracket => UnaryOperator(3, -_)
  70.       case "-" => BinaryOperator(3, _ - _)
  71.       case "*" => BinaryOperator(1, _ * _)
  72.       case "/" => BinaryOperator(1, _ / _)
  73.       case "^" => BinaryOperator(2, scala.math.pow)
  74.       case "sin" => UnaryOperator(3, scala.math.sin)
  75.       case "cos" => UnaryOperator(3, scala.math.cos)
  76.       case "tan" => UnaryOperator(3, scala.math.tan)
  77.       case "sqrt" => UnaryOperator(3, scala.math.sqrt)
  78.       case v => throw new IllegalArgumentException("Unknown constant - " + v)
  79.     }
  80.     res match {
  81.       case OpenedBracket if list.nonEmpty && list.last == ClosedBracket =>
  82.         throw new ArithmeticException("Wrong brackets usage")
  83.       case ClosedBracket if list.nonEmpty && list.last == OpenedBracket =>
  84.         throw new ArithmeticException("Empty brackets")
  85.       case o: Operation if list.nonEmpty && list.last.isInstanceOf[Operation] =>
  86.         throw new ArithmeticException("Two operators in succession")
  87.       case _ =>
  88.     }
  89.     list :+ res
  90.   }
  91.  
  92.   def analyzeSymbol: PartialFunction[Char, (Int, Boolean)] = {
  93.     case d if d.isDigit || d == '.' => (0, true)
  94.     case l if l.isLetter => (1, true)
  95.     case _ => (2, false)
  96.   }
  97.  
  98.   private def splitByTokens(exp: String): List[String] = {
  99.     def _split(rest: String, acc: List[String] = Nil, last: Int = 0): List[String] = {
  100.       if (rest.isEmpty) acc
  101.       else {
  102.         val (t, j) = analyzeSymbol(rest.head)
  103.         if (acc.nonEmpty && t == last && j)
  104.           _split(rest.tail, acc.init :+ (acc.last + rest.head), last)
  105.         else
  106.           _split(rest.tail, acc :+ rest.head.toString, t)
  107.       }
  108.     }
  109.     _split(exp).filterNot(isEmpty)
  110.   }
  111.  
  112.   private def calc(container: Container, token: Token): Container = {
  113.     token match {
  114.       case d: Digit => container.copy(output = container.output add d)
  115.       case o: Operation =>
  116.         if (container.stack.nonEmpty && container.lastIsOperation)
  117.           container.lastOperation match {
  118.             case l if l.priority > o.priority => calc(
  119.               Container(container.output.apply(l), container.stack.tail), token)
  120.             case l =>
  121.               container.copy(stack = o :: container.stack)
  122.           }
  123.         else
  124.           container.copy(stack = o :: container.stack)
  125.       case OpenedBracket =>
  126.         container.copy(stack = OpenedBracket :: container.stack)
  127.       case ClosedBracket =>
  128.         if (container.stackIsEmpty)
  129.           throw new ArithmeticException("Closed bracket has no pair")
  130.         if (container.stack.head == OpenedBracket)
  131.           container.copy(stack = container.stack.tail)
  132.         else
  133.           calc(Container(container.output.apply(container.lastOperation),
  134.             container.stack.tail), token)
  135.       case x =>
  136.         throw new UnsupportedOperationException("Unknown operation - " + x)
  137.     }
  138.   }
  139.  
  140.   def eval(expression: String): String = {
  141.     expression + " = " + splitByTokens(expression).
  142.       foldLeft(List[Token]())(tokenize).
  143.       foldLeft(Container())(calc).
  144.       result
  145.   }
  146.  
  147.   def interact(): Unit = {
  148.     val in = scala.io.StdIn.readLine("Expression: ")
  149.     if (in == null || in.isEmpty) return
  150.     try {
  151.       println(eval(in))
  152.     } catch {
  153.       case e: Exception => println("Error: " + e.getMessage)
  154.     }
  155.     interact()
  156.   }
  157.  
  158.   println("Type mathematical expression and press ENTER to evaluate or use blank line to exit")
  159.   println("Available operators and functions: + - / * ^ sin cos tan sqrt")
  160.   println()
  161.   println("Examples:")
  162.   println(eval("4 ^ (-5)"))
  163.   println(eval("-(sqrt((cos(pi * 2) + 1) ^ 8 / 4))"))
  164.   println()
  165.  
  166.   interact()
  167.  
  168. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement