Last active
May 9, 2016 09:11
-
-
Save shashir/411bdc95578d72a8ca955fc738af8dfe to your computer and use it in GitHub Desktop.
Function parser
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
abstract class Exp { | |
def evaluate(x: Double): Double | |
def derivative(): Exp | |
def toStringCanonical(): String | |
} | |
object Exp { | |
def main(args: Array[String]): Unit = { | |
println(parse("2*x^2").derivative().toStringCanonical()) | |
} | |
def parse(input: String): Exp = { | |
val s = input.filter(_ != ' ').replaceAll("(?<!(\\+|\\^|\\*))\\-", "+-").replaceAll("(?<!\\*)\\/", "*/") | |
if (firstLastParensBalance(s)) { | |
return new ParenExp(parse(s.tail.substring(0, s.length - 2))) | |
} | |
val terms: List[String] = splitParts(s, '+') | |
if (terms.size == 0) { | |
return new Value(0) | |
} else if (terms.size > 1) { | |
return new SumExp(terms.map(parse(_))) | |
} | |
if (terms.head.startsWith("-")) { | |
return new NegativeExp(parse(terms.head.tail)) | |
} | |
val factors: List[String] = splitParts(s, '*') | |
if (factors.size == 0) { | |
throw new IllegalArgumentException("Unknown token: " + s) | |
} | |
if (factors.size > 1) { | |
return new ProductExp(factors.map(parse(_))) | |
} | |
if (s.contains("^")) { | |
val i = s.indexOf("^") | |
return new PowerExp(parse(s.substring(0, i)), parse(s.substring(i + 1))) | |
} | |
if (factors.head.startsWith("/")) { | |
return new ReciprocalExp(parse(factors.head.tail)) | |
} | |
if (s.matches("\\d*\\.*\\d*")) { | |
return new Value(s.toDouble) | |
} else if (s == "x") { | |
return Variable | |
} | |
throw new IllegalArgumentException("Unknown token: " + s) | |
} | |
def splitParts(input: String, delim: Char): List[String] = { | |
var parts: List[String] = List() | |
var part: String = "" | |
var level: Int = 0 | |
for (c <- input) { | |
if (c == delim && level == 0) { | |
if (part != "") { | |
parts = parts.:+(part) | |
} | |
part = "" | |
} else if (c == '(') { | |
level += 1 | |
} else if (c == ')') { | |
level -= 1 | |
} | |
if (c != delim || level != 0) { | |
part = part + c | |
} | |
} | |
if (part != "") { | |
parts.:+(part) | |
} else { | |
return parts | |
} | |
} | |
def firstLastParensBalance(input: String): Boolean = { | |
if (input.head != '(' || input.last != ')') { | |
return false | |
} | |
var level: Int = 0 | |
for (i <- 0 until input.length) { | |
if (input.charAt(i) == '(') { | |
level += 1 | |
} else if (input.charAt(i) == ')') { | |
level -= 1 | |
} | |
if (level == 0 && i < input.length - 1) { | |
return false | |
} | |
} | |
return true | |
} | |
} | |
case class NegativeExp(exp: Exp) extends Exp { | |
override def evaluate(x: Double): Double = { | |
return -1 * exp.evaluate(x) | |
} | |
override def toStringCanonical(): String = { | |
return "-" + exp.toStringCanonical() | |
} | |
override def derivative(): Exp = { | |
return NegativeExp(exp.derivative()) | |
} | |
} | |
case class ParenExp(exp: Exp) extends Exp { | |
override def evaluate(x: Double): Double = { | |
return exp.evaluate(x) | |
} | |
override def toStringCanonical(): String = { | |
return "(" + exp.toStringCanonical() + ")" | |
} | |
override def derivative(): Exp = { | |
return exp.derivative() | |
} | |
} | |
case class PowerExp(base: Exp, exponent: Exp) extends Exp { | |
override def evaluate(x: Double): Double = { | |
return Math.pow(base.evaluate(x), exponent.evaluate(x)) | |
} | |
override def toStringCanonical(): String = { | |
return base.toStringCanonical() + "^" + exponent.toStringCanonical() | |
} | |
override def derivative(): Exp = { | |
if (exponent.toStringCanonical().contains("x")) { | |
??? | |
} else { | |
val exponentValue = exponent.evaluate(0.0) | |
return ProductExp(List(Value(exponentValue), PowerExp(base, Value(exponentValue - 1)))) | |
} | |
} | |
} | |
case class ProductExp(factors: List[Exp]) extends Exp { | |
override def evaluate(x: Double): Double = { | |
return factors.map(_.evaluate(x)).reduce(_ * _) | |
} | |
override def toStringCanonical(): String = { | |
return factors.map(_.toStringCanonical()).mkString("*") | |
} | |
override def derivative(): Exp = { | |
if (factors.size == 2) { | |
val a = factors.head | |
val b = factors.last | |
return ParenExp(SumExp(List( | |
ProductExp(List(a.derivative(), b)), | |
ProductExp(List(a, b.derivative())) | |
))) | |
} | |
return ParenExp(SumExp(List( | |
ProductExp(factors.tail.+:(factors.head.derivative())), | |
ProductExp(List(ProductExp(factors.tail).derivative(), factors.head)) | |
))) | |
} | |
} | |
case class ReciprocalExp(exp: Exp) extends Exp { | |
override def evaluate(x: Double): Double = { | |
return 1.0 / exp.evaluate(x) | |
} | |
override def toStringCanonical(): String = { | |
return "1.0/" + exp.toStringCanonical() | |
} | |
override def derivative(): Exp = ??? | |
} | |
case class SumExp(terms: List[Exp]) extends Exp { | |
override def evaluate(x: Double): Double = { | |
return terms.map(_.evaluate(x)).sum | |
} | |
override def toStringCanonical(): String = { | |
return terms.map(_.toStringCanonical()).mkString(" + ") | |
} | |
override def derivative(): Exp = { | |
return SumExp(terms.map(_.derivative())) | |
} | |
} | |
case class Value(v: Double) extends Exp { | |
override def evaluate(x: Double): Double = { | |
return v | |
} | |
override def toStringCanonical(): String = { | |
return v.toString | |
} | |
override def derivative(): Exp = { | |
return Value(0) | |
} | |
} | |
object Variable extends Exp { | |
override def evaluate(x: Double): Double = { | |
return x | |
} | |
override def toStringCanonical(): String = { | |
return "x" | |
} | |
override def toString(): String = { | |
return "Variable" | |
} | |
override def derivative(): Exp = { | |
return Value(1.0) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment