#scala #implementation #cas #computer-algebra-systems
#scala #реализация #cas #компьютерная алгебра-системы
Вопрос:
Я ищу простую систему CAS для scala.
Она должна обладать следующими функциями:
- предоставить доступ к абстрактному синтаксическому дереву (предпочтительно через классы case для упрощения сопоставления)
- разбор
String
в AST - упрощение выражений
Если ничего не существует, и я должен написать что-то базовое сам, каково наилучшее представление?
Я думаю о чем-то вроде этого:
abstract trait Term
{
def simplify:Term
def evaluate(assignment:Var => Double):Double
def derivative:Term
}
case class Const(c:Int) extends Term
case class Var(x:String) extends Term
case class Negate(x:Term) extends Term
case class Subtract(x:Term, y:Term) extends Term
case class Divide(x:Term, y:Term) extends Term
object Add { def apply(x:Term*):Add = Add(x.toList) }
case class Add(xs : List[Term]) extends Term
object Multiply { def apply(x:Term*):Multiply = Multiply(x.toList) }
case class Multiply(xs:List[Term]) extends Term
case class Power(x:Term, y:Term) extends Term
case class Exp(x:Term) extends Term
Я бы реализовал алгоритм упрощения, описанный здесь, который кажется утомительным. (Но, может быть, скука неизбежна, когда дело доходит до упрощения алгебраических выражений?)
Некоторые критические замечания по поводу этой конкретной реализации являются:
- Я буду рекурсивно вызывать
simplify
повсюду аргументы для классов case (кажется, это можно было бы как-то централизовать) - Работа с varargs /
List
аргументами дляAdd
иMutliply
похоже, что это может привести к беспорядку
Ответ №1:
Я не знаю о существующем CAS для Scala.
При выполнении языковой обработки я обычно нахожу гораздо более приятным использовать сопоставление шаблонов по закрытой иерархии, а не полиморфизм в стиле OO. Поскольку добавление новых типов терминов встречается редко (что означает смену языка) и добавление новых операций является обычным делом, эта сторона проблемы выражения, по-видимому, подходит лучше.
sealed trait Term
case class Const(c : Double) extends Term
case class Var(x : String) extends Term
case class Negate(x : Term) extends Term
case class Multiply(xs : List[Term]) extends Term
// etc
object CAS {
// I assume that the assignment map may be incomplete, thus
// evaluation is really a partial substitution and then simplification
def evaluate(t : Term, assignment : Var => Option[Double]) : Term = t match {
case _ : Const => t
case v : Var => assignment(v) map Const getOrElse v
case Negate(x) => evaluate(Multiply(Const(-1) :: evaluate(x, assignment) :: Nil), assignment)
case Multiply(ts) => {
val evalTs = ts map { t => evaluate(t, assignment) }
val flattened = evalTs flatMap {
case Multiply(subs) => subs
case t => List(t)
}
val constTotal = Const((flattened collect { case Const(c) => c }).product)
val otherTerms = flattened filter { case t : Const => false; case _ => true }
(constTotal, otherTerms) match {
case (Const(0), _) => Const(0)
case (Const(1), Nil) => Const(1)
case (Const(1), _) => Multiply(otherTerms)
case _ => Multiply(constTotal : otherTerms)
}
}
// etc
}
private val emptyAssignment : (Var => Option[Double]) = { x : Var => None }
// simplfication is just evaluation with an empty assignment
def simplify(t : Term) : Term = evaluate(t, emptyAssignment)
}
Одна из технологий, о которой я хотел узнать, но не успел, — это грамматики атрибутов. Предполагается, что они избавят от значительной части скуки, связанной с такого рода обработкой AST. Смотрите kiama http://code.google.com/p/kiama для реализации Scala
Кстати, хотя я и использую double здесь для вашего домена, вам, возможно, было бы лучше использовать «big rational» — пару BigIntegers. Они медленные, но очень точные.
Комментарии:
1. Спасибо! Есть какие-нибудь советы о том, как упростить выражения вида:
((a * c * x^2) (b * x^2))
быть(a*c b) * x^2
? С аналогом было легко работатьPower
, но списки для обоихMultiply
иAdd
делают это сложным для меня.2. Статья, на которую вы ссылаетесь, описывает аналогичное преобразование из (x ^ a * y ^ b * x ^ c) в (x ^ (a c) * y ^ b). Подход, который они используют, прост: убедившись, что все дочерние узлы умножения имеют одинаковую каноническую форму, они извлекают первый элемент из списка и смотрят, имеют ли какие-либо другие узлы такую же базу. Если это так, они объединяют эти два. Затем они делают то же самое с остальным. Списки Scala имеют удобную функцию под названием «partition», которая позволит вам разделить список в соответствии с произвольными критериями, которые должны позволить вам сделать именно это.