X-Beliebige Funktion im Programm darstellen und Werte berechnen
-
Hallo,
angenommen es sei Ziel, ein Modul zu erstellen, welches jede beliebige Funktion als Objekt beinhalten kann, und auch Werte berechnen kann. Ein Beispielprogramm:
> > f(x) = x^2 // Programm speichert den Funktionsterm x^2 unter dem Namen f ab > f(2) 4 > g(x) = x^2 / sin(x) // sin sei hierbei schon "eingebaut" als std::sin > h(x) = e^(2*x^2 - 7*x +5) + 25*x^3Das könnte man z.B. in einem Programm verwenden, welches eine Funktion nimmt und plottet, oder ein CLI Taschenrechner, der eben oben gezeigte Funktionalität erlaubt.
Ich habe schon ein paar Überlegungen dazu angestellt: Ganz banal wäre ja, den Funktionsterm als string zu speichern, und dann im warsten Sinne des Wortes "einsetzten" (Der Grund, weshalb ich auf sowas komme, ist dass ich schon einen fertigen MatheParser hatte, für einen Taschenrechner):
class Function { public: Function() = default; virtual ~Function() { } virtual double operator()(double arg) const = 0; }; class StrFunction : public Function { public: StrFunction(const std::string& term, const std::string& var, MathParser& parser) :m_term{term}, m_var{var}, m_parser{parser} { } double operator()(double arg) const override { replaceAll(m_term, m_var, std::to_string(arg)); // Alle vorkommenden z.B. 'x' durch die Zahl ersetzten. return parser.expr(m_term); // der MatheParser berechnet das Ergebnis des entstandenen Ausdrucks } private: std::string m_term; std::string m_var; MathParser& m_parser; };Vorteil hiervon ist, dass das Parsen relativ einfach ist, wenn man schon den MatheParser hat. Was performance angeht, vermutlich nicht optimal, ist aber auch nicht so schlimm.
Natürlich auch mal schnell gegoogelt, irgendwann stößt man auf Polynome darstellen, und das ist ja noch vertretbar:
class Polynomial : public Function { public: Polynomial() = default; void add_polynomial(double coeff, double exp) { if (polys.find(exponent) != polys.end()) polys[exponent] += coefficient; else polys[exponent] = coefficient; } double operator()(double arg) const override { assert(!polys.empty()) double result {}; for (const auto& p : polys) result += p.second * std::pow(arg, p.first); return result; } private: // key: exponent, value: coefficient std::map<double, double> polys; };Geht vielleicht noch besser, war auch nur der erste Versuch. Dann will ich aber auch
e^xdarstellen, mmh glaube das geht nicht mit Polynomen, also: Exponential Funktion:class Exponential : public Function { public: Exponential(double b):base{b} { } double operator()(double arg) const override { return std::pow(base, arg); } private: double base; };Jetzt das highlight
sin(5x^2-27x+5)outsch, wie soll das denn gehen. Naja kein Problem, das macht man doch mit Verkettung oder?class Chain : public Function { public: Chain(Function& innerFunc, Function& outerFunc) :inner{innerFunc}, outer{outerFunc} { } double operator()(double arg) const override { return outer(inner(arg)); } private: Function& inner; Function& outer; };Das wird also langsam etwas umfangreich, und wie man das ganze parst ist dabei noch lange nicht gelöst. Ich frage mich aber auch, wenn eine professionelle Software sowas macht, oder meinetwegen der GTR, wie machen die es? Der GTR (Graphischer Taschenrechner) kann ja auch alles plotten und rechnen was man dem reinschmeißt.
Ich würde das zur Übung auch gerne versuchen (natürlich erstmal nur Polynome etc.), bin mir aber nicht sicher wie aufwendig/kompliziert das tatsächlich ist.
Klar, wenn es nur darum ginge das Problem zu lösen, würde ich schon lange glücklich mit der ersten Version rumrennen. Ich möchte aber das Rad neu erfinden, zwecks Lerneffekt.
LG
P.S.: Entschuldigt den langen Post
-
wenn ich mich recht erinnere, dann hat bjarne stroustrup in "die c++ programmiersprache" einen haufen an beispiel-code und übungen zum parsen und korrekten darstellen solcher funktionen (und dieses buch solltest du sowieso dein eigen nennen).
mich würde ja interessieren, wie dein MatheParser arbeitet, denn dort liegt wahrscheinlich bereits die (eine) lösung für das problem, nämlich ein binary expression tree (oder etwas in der art).
-
Du könntest dir auch z.B. mal https://en.wikipedia.org/wiki/Shunting-yard_algorithm angucken. Gibt bestimmt auch noch andere Möglichkeiten.
-
Hi,
der Parser arbeitet wie beim Stroustrup, recursive decent und eben für jedes Ding ne Funktion "expr", "term", "prim" etc. Du meinst nicht den Desktop Calculator, den er zeigt, oder? Ich schau nochmal nach, sowohl wie im Wikipedia Artikel.
LG
P.S.: Ich möchte Funktionen in Software aufstellen, nicht mathematische Ausdrücke parsen (siehe meine Codebeispiele).
EDIT: Da ich zwar mathematische Ausdrücke parsen kann, ich es aber nicht hinbekomme mit relativ wenig Aufwand auch Funktionen zu parsen, bleibe ich vermutlich bei der "einfachen" Funktionsterm-als-string-speichern-und-einsetzen Variante. Ich war so weit gekommen, dass ich Polynome parsen konnte, das reicht aber nicht ganz aus, und wird auch relativ schnell sehr komplex (Wenn jetzt auch noch Exponentialfunktionen und Verkettungen z.B: dazu kommen sollen).