0

If I have a string that has an arithmetic expression with paranthesis like:

((4 * 7) / 2) - 7

How do I evaluate it automatically? In particular with Kotlin. I heard that you need to make a parser, so how can I do so in Kotlin and have all the necessary basic operations in the example as such?

Jiehfeng
  • 753
  • 7
  • 16
  • 1
    [Shunting yard algorithm](https://en.wikipedia.org/wiki/Shunting-yard_algorithm) is a good starting place. – Silvio Mayolo Mar 21 '22 at 15:47
  • Practically [the same question](/questions/71483955/arithmetic-parser-in-kotlin) was asked last week. [And again](/questions/71466439/convert-arithmetic-expression-of-a-string-type-to-an-integer-type) the day before that… – gidds Mar 21 '22 at 16:51
  • @gidds It seems so, but you have linked to answers that eventually lead to a question answered regarding Java and the solution being built in Javascript. I believe my question is valid enough given my requirements, I want to know how to do this in vanilla Kotlin. – Jiehfeng Mar 21 '22 at 20:35
  • 1
    @Jiehfeng Fair enough. (Though I think it can still be helpful to link to other questions and explain why this isn't a duplicate.) I'm curious, though (since it's not the first time I've seen very similar questions posted in quick succession). Do you know of any connection with the previous questions, or is it just an interesting coincidence? – gidds Mar 21 '22 at 20:45
  • @gidds Noted, I'll see what I can do tomorrow. As for those questions, certainly haha. It seems they're from the same university as I am, there's a coursework due tomorrow so the timing and questions have the uncanny resemblance to the coursework specification, especially with how new they are to questioning and how recent their accounts are... Apologies about this, this is most definitely the last given the deadline. – Jiehfeng Mar 21 '22 at 20:51

2 Answers2

2
fun evaluate(str: String): Double {

  data class Data(val rest: List<Char>, val value: Double)

  return object : Any() {

    fun parse(chars: List<Char>): Double {
      return getExpression(chars.filter { it != ' ' })
        .also { if (it.rest.isNotEmpty()) throw RuntimeException("Unexpected character: ${it.rest.first()}") }
        .value
    }

    private fun getExpression(chars: List<Char>): Data {
      var (rest, carry) = getTerm(chars)
      while (true) {
        when {
          rest.firstOrNull() == '+' -> rest = getTerm(rest.drop(1)).also { carry += it.value }.rest
          rest.firstOrNull() == '-' -> rest = getTerm(rest.drop(1)).also { carry -= it.value }.rest
          else                      -> return Data(rest, carry)
        }
      }
    }

    fun getTerm(chars: List<Char>): Data {
      var (rest, carry) = getFactor(chars)
      while (true) {
        when {
          rest.firstOrNull() == '*' -> rest = getTerm(rest.drop(1)).also { carry *= it.value }.rest
          rest.firstOrNull() == '/' -> rest = getTerm(rest.drop(1)).also { carry = it.value / carry }.rest
          else                      -> return Data(rest, carry)
        }
      }
    }

    fun getFactor(chars: List<Char>): Data {
      return when (val char = chars.firstOrNull()) {
        '+'              -> getFactor(chars.drop(1)).let { Data(it.rest, +it.value) }
        '-'              -> getFactor(chars.drop(1)).let { Data(it.rest, -it.value) }
        '('              -> getParenthesizedExpression(chars.drop(1))
        in '0'..'9', ',' -> getNumber(chars)
        else             -> throw RuntimeException("Unexpected character: $char")
      }
    }

    fun getParenthesizedExpression(chars: List<Char>): Data {
      return getExpression(chars)
        .also { if (it.rest.firstOrNull() != ')') throw RuntimeException("Missing closing parenthesis") }
        .let { Data(it.rest.drop(1), it.value) }
    }

    fun getNumber(chars: List<Char>): Data {
      val s = chars.takeWhile { it.isDigit() || it == '.' }.joinToString("")
      return Data(chars.drop(s.length), s.toDouble())
    }

  }.parse(str.toList())

}

val expresssion = "((4 * 7) / 2) - 7"

val result = evaluate(expresssion)

println(result)   // Output: 7.0
lukas.j
  • 6,453
  • 2
  • 5
  • 24
  • I’ve seen a couple of your answers like this and am just curious. What is the purpose of creating an anonymous object to wrap all your functions? It seems like it just creates extra nesting and I don’t see the benefit. – Tenfour04 Mar 21 '22 at 16:11
  • 1
    Because of the circular calls like _getExpression -> getTerm -> getFactor -> getParenthesizedExpression -> getExpression_. Since there are no forward declarations in Kotlin I use this kind of solution to 'squeeze' everything into a function. – lukas.j Mar 21 '22 at 16:17
0

Look up "Math parser" on google. These are various dependencies you can add that allow you to then parse string input into mathematical formulas.

Stefan N
  • 53
  • 7
  • This does not provide an answer to the question. Once you have sufficient [reputation](https://stackoverflow.com/help/whats-reputation) you will be able to [comment on any post](https://stackoverflow.com/help/privileges/comment); instead, [provide answers that don't require clarification from the asker](https://meta.stackexchange.com/questions/214173/why-do-i-need-50-reputation-to-comment-what-can-i-do-instead). - [From Review](/review/late-answers/33888415) – Ihor Baklykov Feb 24 '23 at 11:21