Skip to content

Instantly share code, notes, and snippets.

@shashir
Last active May 9, 2016 09:11
Show Gist options
  • Save shashir/411bdc95578d72a8ca955fc738af8dfe to your computer and use it in GitHub Desktop.
Save shashir/411bdc95578d72a8ca955fc738af8dfe to your computer and use it in GitHub Desktop.
Function parser
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