Вычисление логических выражений в строке внутри Java-Scala-Kotlin кода

в 13:41, , рубрики: expression, groovy, java, jvm, kotlin, string

Мне нужно было в рантайме вычислять истинность выражений типа

a>10 && b<c+5 && (a+b)<c*4

находящихся в строке Скалы.

Сам код у меня на Скале, но оценку разных библиотек для этого я делал на Котлине, просто чтобы поиграться с ним. Само выражение я получаю от клиента, но от внутреннего, поэтому мне не надо было заботиться о том, чтобы в выражении мне нe стерли файлы с диска.

Я оценивал разные библиотеки на то 1) могу ли они сделать то, что надо 2) скорость исполнения

Были проверены

  • интерполяция строк
  • Js Engine
  • javaluator
  • exp4j
  • evalEx
  • mxparser
  • MathEval
  • Groovy

Результаты

Время пробега в мс для 1000 выражений (вернее одно и тоже выражение для 1000 разных набoров 3х переменных):

js 239 ms
mxParser 56713 ms
evalex 35 ms
groovy 9910 ms

Остальные способы/библиотеки не сработали.

Под катом подробности:

1. Интерполяция строк.

Сначала я думал, нельзя ли приспособить интерполяцию строк Скалы для моих целей.
Я могу в скале написать

s"{a>10}"

но я не смог найти способ превратить обычную строку в строку для интерполяции.
В Скале есть StringContext, который чередует обычные строки с переменными:

s"You are ${age / 10} decades old, $name!"

это на самом деле

StringContext ("You are ", " decades old, ", "!").s (age / 10, name)

и превращается в

"You are " + (age / 10) + " decades old, " + (name) + "!"

но не хотелось возиться с парсингом строки и разделением ее на части

2. Использовать JavaScript Engine внутри Java.

работало без проблем

val e = ScriptEngineManager().getEngineByName("js")
fun jsEvaluate(a: Double, b: Double, c: Double): Boolean {
        e.context.setAttribute("a", a, ScriptContext.ENGINE_SCOPE)
        e.context.setAttribute("b", b, ScriptContext.ENGINE_SCOPE)
        e.context.setAttribute("c", c, ScriptContext.ENGINE_SCOPE)
        return e.eval(expr) as Boolean
}

3. Библиотека Javaluator

Без того, чтобы писать свои расширения, поддерживает только выражения с плавающей точкой.
С легкостью можно расширить для булевских выражений, и, наверное, можно расширить и для выражений типа "(a+b)>5"

4. Библиотека mxParser

Всё делает, но медленее чем JavaScript примерно в 1000 раз.
Результат всегда возвращает как Double.

val mxExpr = org.mariuszgromada.math.mxparser.Expression(expr)

fun mxParserEvaluate(a: Double, b: Double, c: Double): Boolean {
        val v1 = Argument("a = $a")
        val v2 = Argument("b = $b")
        val v3 = Argument("c = $c")
        mxExpr.addArguments(v1, v2, v3)
        return mxExpr.calculate() == 1.0
    }

5. Библиотека evalEx

Всё делает, самая быстрая, результат возвращает как BigDecimal

 val evalExpression = com.udojava.evalex.Expression(expr)
 
   fun evalexEvaluate(a: Double, b: Double, c: Double): Boolean {
        val eval = evalExpression.with("a", BigDecimal.valueOf(a)).and("b", BigDecimal.valueOf(b)).and("c", BigDecimal.valueOf(c)).eval()
        return eval == BigDecimal.ONE
    }

6. Библиотека exp4j

Нет поддержки для логических выражений, только выражения с плавающей точкой, но может быть расширена.

7. Библиотека MathEval

Нет поддержки для логических выражений, только выражения с плавающей точкой

8. Groovy

Всё просто и всё работает, но медленно

 fun groovyEvaluate(a: Double, b: Double, c: Double): Boolean {
        val binding = Binding()
        binding.setVariable("a", a)
        binding.setVariable("b", b)
        binding.setVariable("c", c)


        val sh = GroovyShell(binding)
        return sh.evaluate(expr) as Boolean
    }

Код, которым я проверял можно взять на гитхабе

Автор: javax

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js