CMath Parser-アマチュア゚クスペリ゚ンス

コンピュヌタヌず人間-私たちがお互いを理解するこずはどれほど難しいか。 実際、プログラミングプロセスは、マシンが理解できる蚀語で、マシンに必芁なものを説明するものです。


はじめに



私の仕事では、たた趣味ずしお、数孊蚈算に関連するコヌドを曞くプロセスに関䞎しおいたす。 最埌のタスクの1぀は、デヌタを蚈算、芖芚化、およびいく぀かの数匏を最適化するずきに、ナヌザヌが独自に入力しお䜿甚できる゜フトりェアを䜜成するこずでした。 そしお、特別な数孊関数のコヌドラむブラリを絶えず補完する私の自然な怠andさず䞍本意を考慮しお、考えが思い浮かびたした-クレむゞヌな孊生のアむデアを実装し、数匏甚のパヌサヌバむクを発明しないのはなぜですか



もちろん、本発明のプロセスに着手する前に再び、普遍的な怠inessを考慮しお、既存の実装をテヌマにしたYandexずGoogleのかなり長いレむプがありたした。 そしおもちろん、圌らは非垞に倚く発芋されたした。 しかし、残念ながら、特定の実装から達成したかったものは芋぀かりたせんでした。 怜玢条件は次のずおりです。







自転車䜜りの目暙



実際、自転車の発明の䞻な目暙は、䞀連の数匏をデリゲヌトにコンパむルしお、倀を蚈算するプロセスを高速化する可胜性でした。



残念ながら、怜玢゚ンゞンで䜜業するための私のわずかな胜力、時間、劎力、怠ofの欠劂は、プロトタむプの怜玢で肯定的な結果をもたらさなかったため、熊手旅行に着手するこずにしたした。



モデルず䞻なアむデア



オブゞェクトモデルは、パヌサヌクラスず数匏クラスの2぀のクラスで構成されたす。 内郚では、さらに3぀のクラスが䜿甚されたす数孊匏ツリヌクラス、mat.expressionツリヌの抜象ノヌドクラス、およびmat.expression文字列の論理芁玠のクラス-甚語。 さらに、関数、倉数、関数のクラス、および関数、倉数、および関数のコレクションがそれぞれ実装されたすこれらは数孊のクラスに埋め蟌たれたす。



発芋、たたは解決策の最適化のふりをしおいないが、問題の解決策にアプロヌチする詊みであったアむデアは、mat.expression文字列の文字の初期シヌケンスをいく぀かの論理コンポヌネントに分解するこずでした。 型付けされた数匏の論理ブロックの階局シヌケンスを圢成したす。 そしお、それに基づいお、mat.expressionのツリヌを構築したす。



蚭蚈は、「どのように完成させお、䜿いやすく䟿利にしたいか」ずいうアむデアから始たりたした。 次のナヌスケヌスを実装したいず思いたした。



パヌサヌオブゞェクトが䜜成され、プレれンテヌションモデルのレベルたたはビゞネスロゞックのどこかに修正されたす。 それは構成されたす必芁な定数がそれに远加され、その埌機胜する関数が定矩されたす。 䞍明な関数および倉数を凊理するために、むベントサブスクラむバヌが远加されたす。 そしお、パヌサヌはParcestringメ゜ッドの呌び出しを埅っおいたす。



メむンのParceメ゜ッドでは、マット匏文字列が入力ずしお枡され、その結果はマット匏ツリヌを含むマット匏オブゞェクトになりたす。



mat.expressionのオブゞェクトには、その䞭にある関数、倉数、および定数のコレクションが必芁です。 これらのオブゞェクトの倀を倉曎する可胜性、およびその修正を目的ずしお匏ツリヌに圱響を䞎える可胜性があるはずです。 数匏のオブゞェクトには、数匏のツリヌをトラバヌスするこずにより倀を蚈算するメ゜ッドが必芁です。このメ゜ッドは、入力倉数のセットをパラメヌタヌずしお受け取り、結果ずしお数倀を生成したす。



数匏オブゞェクトには、数匏ツリヌをSystem.Linq.Expressionオブゞェクトに倉換するメ゜ッドが必芁です。 そしお、Linq.Expressionメカニズムを䜿甚しおすぐにコンパむルされたデリゲヌトを取埗するメ゜ッド。



残念ながら、類䌌した䜕かの既補の実装も、そのようなパヌサヌの䜜成をある皋床説明する方法も、どこにも説明されおいたせん。



オブゞェクトモデルの説明



パヌサヌクラス




オブゞェクトの䜜成䜜成埌は、Parseメ゜ッドの呌び出しから始たりたす。



パブリックMathExpression解析文字列StrExpression
/// <summary>   </summary> /// <param name="StrExpression">   </param> /// <returns> </returns> [NotNull] public MathExpression Parse([NotNull] string StrExpression) { Contract.Requires(!string.IsNullOrWhiteSpace(StrExpression)); Contract.Ensures(Contract.Result<MathExpression>() != null); StrPreprocessing(ref StrExpression); OnStringPreprocessing(ref StrExpression); var expression = new MathExpression(StrExpression, this); ProcessVariables(expression); ProcessFunctions(expression); return expression; }
      
      





契玄を省略するず、圌の仕事の意味は、文字列の前凊理、数匏のコンストラクタヌの呌び出し、および倉数ず関数の分析のためのこの匏の埌凊理に限定されたす。



前凊理には2぀の段階がありたす。



文字列から䜙分な文字を削陀するプラむベヌトStrPreprocessingメ゜ッド



保護されたvoid StrPreprocessing参照文字列Str
 /// <summary>    </summary> /// <param name="Str"> </param> //     ,     protected virtual void StrPreprocessing([NotNull] ref string Str) { Contract.Requires(!string.IsNullOrEmpty(Str)); Contract.Ensures(!string.IsNullOrEmpty(Contract.ValueAtReturn(out Str))); Str = new string(Str.Where(f_ExcludeCharsSet.NotContains).ToArray()); }
      
      







パヌサヌナヌザヌが分析のために文字列を独立しお準備できるように、文字列前凊理むベントを生成するメ゜ッド



パブリックむベントEventHandler <EventArgs <string >> StringPreprocessing
 /// <summary>   </summary> public event EventHandler<EventArgs<string>> StringPreprocessing; /// <summary>    </summary> /// <param name="args"> ,   </param> protected virtual void OnStringPreprocessing([NotNull] EventArgs<string> args) { Contract.Requires(args != null); Contract.Requires(args.Argument != null); Contract.Requires(args.Argument != string.Empty); Contract.Ensures(args.Argument != null); Contract.Ensures(args.Argument != string.Empty); StringPreprocessing?.Invoke(this, args); } /// <summary>    </summary> /// <param name="StrExpression"> </param> private void OnStringPreprocessing([NotNull] ref string StrExpression) { Contract.Requires(!string.IsNullOrEmpty(StrExpression)); Contract.Ensures(Contract.ValueAtReturn(out StrExpression) != null); Contract.Ensures(Contract.ValueAtReturn(out StrExpression) != string.Empty); var args = new EventArgs<string>(StrExpression); OnStringPreprocessing(args); StrExpression = args.Argument; }
      
      







行のガベヌゞがクリアされ、解析の準備が敎うず、匏のコンストラクタヌに枡されたす。 関数、定数、倉数の定矩を担圓するパヌサヌ自䜓も枡されたす。



そのメンバヌが蚀及されるず、パヌサヌクラスに戻りたす。そしお、...数孊衚珟のクラス



チャヌト




Parseメ゜ッドからの呌び出しが枡されるコンストラクタヌ



内郚MathExpression文字列StrExpression、ExpressionParserパヌサヌ
 /// <summary>   </summary> /// <param name="StrExpression">  </param> /// <param name="Parser">  </param> internal MathExpression([NotNull] string StrExpression, [NotNull] ExpressionParser Parser) : this() { Contract.Requires(!string.IsNullOrEmpty(StrExpression)); Contract.Requires(Parser != null); Contract.Ensures(Tree != null); var terms = new BlockTerm(StrExpression); //     var root = terms.GetSubTree(Parser, this); //       f_ExpressionTree = new ExpressionTree(root); //      }
      
      







繰り返したすが、コントラクトブロックを省略するず、最初に、行に基づいお、匏の甚語の階局オブゞェクト構造が䜜成されたす。 次に、最初のブロックから、mat.expressionのツリヌのルヌトを取埗するメ゜ッドが呌び出されたす。 そしお、それに基づいおツリヌコンストラクタヌが機胜したす。



数匏の分析の最初の段階では、文字列衚珟文字のシヌケンスを論理ブロックのシヌケンスに結合する必芁がありたす。 それらのいく぀かは、互いに再垰的にネストできたす。



匏甚語クラスの階局




行は4皮類の甚語に分かれおいたす。





したがっお、最初は行党䜓が1぀のブロック甚語ずしお衚され、その䞭に構成芁玠が含たれたす。



public BlockTerm文字列Str
 /// <summary>   </summary> /// <param name="Str">  </param> public BlockTerm(string Str) : this("", Str, "") { } /// <summary>  </summary> /// <param name="OpenBracket"> </param> /// <param name="Str">  </param> /// <param name="CloseBracket"> </param> public BlockTerm([NotNull] string OpenBracket, [NotNull] string Str, [NotNull] string CloseBracket) : base(string.Format("{0}{2}{1}", OpenBracket ?? "", CloseBracket ?? "", Str)) { Contract.Requires(!string.IsNullOrEmpty(Str)); f_OpenBracket = OpenBracket; f_CloseBracket = CloseBracket; f_Terms = GetTerms(Str); }
      
      







基本的なテルマクラス



抜象クラスTerm {...}
 /// <summary>  </summary> abstract class Term { /// <summary> </summary> protected string f_Value; /// <summary>   </summary> /// <param name="Value"> </param> protected Term(string Value) { f_Value = Value; } /// <summary>       </summary> /// <param name="Parser">  </param> /// <param name="Expression"> </param> /// <returns>  .,     </returns> [NotNull] public abstract ExpressionTreeNode GetSubTree([NotNull] ExpressionParser Parser, [NotNull] MathExpression Expression); /// <summary>   .</summary> /// <returns>   .</returns> public override string ToString() => f_Value; }
      
      







郚分文字列の構成芁玠ぞの内蚳は、GetTermsメ゜ッドによっお実行されたす。



private static Term [] GetTerms文字列Str
 /// <summary>      </summary> /// <param name="Str">   </param> /// <returns>   </returns> [CanBeNull] private static Term[] GetTerms([CanBeNull] string Str) { if(Str == null) return null; if(Str.Length == 0) return new Term[0]; var pos = 0; var len = Str.Length; var result = new List<Term>(); while(pos < len) { var c = Str[pos]; if(char.IsLetter(c) || c == '∫') { Term value = new StringTerm(GetNameString(Str, ref pos)); if(pos < len) switch(Str[pos]) { case '(': { var blokStr = Str.GetBracketText(ref pos); var block = new BlockTerm("(", blokStr, ")"); value = new FunctionTerm((StringTerm)value, block); } break; case '[': { var blokStr = Str.GetBracketText(ref pos, "[", "]"); var block = new BlockTerm("[", blokStr, "]"); value = new FunctionTerm((StringTerm)value, block); } break; case '{': { var blokStr = Str.GetBracketText(ref pos, "{", "}"); var block = new BlockTerm("{", blokStr, "}"); value = new FunctionTerm((StringTerm)value, block); } break; } if(pos < len && Str[pos] == '{') value = new FunctionalTerm ( (FunctionTerm)value, new BlockTerm("{", Str.GetBracketText(ref pos, "{", "}"), "}") ); result.Add(value); } else if(char.IsDigit(c)) result.Add(new NumberTerm(GetNumberString(Str, ref pos))); else switch(c) { case '(': { var blokStr = Str.GetBracketText(ref pos); var block = new BlockTerm("(", blokStr, ")"); result.Add(block); } break; case '[': { var blokStr = Str.GetBracketText(ref pos, "[", "]"); var block = new BlockTerm("[", blokStr, "]"); result.Add(block); } break; case '{': { var blokStr = Str.GetBracketText(ref pos, "{", "}"); var block = new BlockTerm("{", blokStr, "}"); result.Add(block); } break; default: result.Add(new CharTerm(Str[pos++])); break; } } return result.ToArray(); }
      
      







このメ゜ッドは、空の入力行ずれロ長をチェックするこずから始たりたす。 その埌、行内の分析されたシンボルの珟圚の䜍眮ずその長さが固定され、その埌、珟圚の䜍眮のシンボルは行の終わりに達するたでサむクルで考慮されたす。



-文字たたは敎数の蚘号の堎合、GetNameStringメ゜ッドを䜿甚しお名前をキャプチャしようずしたす。



プラむベヌト静的文字列GetNameString文字列Str、ref int pos
 /// <summary>   </summary> /// <param name="Str"> </param> /// <param name="pos">  </param> /// <returns> </returns> private static string GetNameString([NotNull] string Str, ref int pos) { Contract.Requires(!string.IsNullOrEmpty(Str)); Contract.Ensures(Contract.ValueAtReturn(out pos) >= 0); Contract.Ensures(Contract.ValueAtReturn(out pos) < Str.Length); var result = ""; var L = Str.Length; var i = pos; while(i < L && (char.IsLetter(Str[i]) || Str[i] == '∫')) result += Str[i++]; if(i == L || !char.IsDigit(Str[i])) { pos = i; return result; } while(i < L && char.IsDigit(Str[i])) result += Str[i++]; pos += result.Length; return result; }
      
      







その埌、珟圚の文字に開始ブラケットがあるかどうかがチェックされたす。 ブラケットの1぀が芋぀かった堎合、ネストされたブロックが行から抜出され、開始ブラケットず察応する終了ブラケットによっお制限されたす。 この方法で䜜成されたブロック甚語は、機胜甚語のコンストラクタヌに配眮され、前に蚭定された関数名が瀺されたす。



開き括匧ず閉じ括匧で制限された郚分文字列は、拡匵メ゜ッドによっお文字列から遞択されたす。



public static string GetBracketTextこの文字列Str、ref int Offset、文字列Open、文字列Close
 /// <summary> ///  ,            /// </summary> /// <param name="Str"> </param> /// <param name="Offset"> ///       -         /// </param> /// <param name="Open">  </param> /// <param name="Close">  </param> /// <returns>,       </returns> /// <exception cref="FormatException"> ///      ,        ///       /// </exception> public static string GetBracketText(this string Str, ref int Offset, string Open = "(", string Close = ")") { var Start = Str.IndexOf(Open, Offset, StringComparison.Ordinal); if(Start == -1) return null; var Stop = Str.IndexOf(Close, Start + 1, StringComparison.Ordinal); if(Stop == -1) throw new FormatException(); var start = Start; do { start = Str.IndexOf(Open, start + 1, StringComparison.Ordinal); if(start != -1 && start < Stop) Stop = Str.IndexOf(Close, Stop + 1, StringComparison.Ordinal); } while(start != -1 && start < Stop); if(Stop == -1 || Stop < Start) throw new FormatException(); Offset = Stop + Close.Length; Start += Open.Length; return Str.Substring(Start, Stop - Start); }
      
      







最初に、開始文字ず終了文字の最初の出珟のむンデックスが決定されたす。 最初の文字が芋぀からない堎合は、voidを返したす。 終了文字が芋぀からない堎合、これはフォヌマット゚ラヌです。



この方法の考え方は、行の先頭ず末尟のパタヌンを順番に埪環怜玢するこずです。 次のオヌプニングキャラクタヌを芋぀けようずしおいたす。 芋぀かっお、終了文字の前にある堎合は、終了文字のむンデックスを曎新する必芁がありたす。 サむクルは、開始文字の前にない終了文字があるたで続きたす。



メ゜ッドの結果は、開始文字ず終了文字の間にあるサブストリングです。



圢成された機胜甚語の埌に開始䞭括匧が芋぀かった堎合、これは機胜の本䜓を開始したす。 ブロック内の䞭括匧の内容を遞択し、term-functionalを䜜成し、term-functionを瀺したす。このコンテキストでは、関数の名前ずそのパラメヌタヌが含たれ、本䜓は䞭括匧内のブロックになりたす。



括匧が芋぀からなかった堎合、芋぀かった名前はリテラル将来の倉数...たたは定数です。



-文字列の次の文字が数字の堎合、敎数が始たりたす。 数字のみを含む郚分文字列を遞択したす。



プラむベヌト静的文字列GetNumberString文字列Str、ref int pos
 /// <summary>  </summary> /// <param name="Str"> </param> /// <param name="pos">   </param> /// <returns>  </returns> private static string GetNumberString([NotNull] string Str, ref int pos) { Contract.Requires(!string.IsNullOrEmpty(Str)); Contract.Ensures(Contract.ValueAtReturn(out pos) >= 0); Contract.Ensures(Contract.ValueAtReturn(out pos) < Str.Length); var p = pos; var l = Str.Length; while(p < l && !char.IsDigit(Str, p)) p++; if(p >= l) return null; var start = p; while(p < l && char.IsDigit(Str, p)) p++; pos = p; return Str.Substring(start, p - start); }
      
      







このメ゜ッドの結果-数字を含む文字列-は、敎数項のコンストラクタヌに分類されたす。



-行の次の文字が開き括匧の堎合、ブロックが始たりたす。 GetBracketText拡匵メ゜ッドでそのサブストリングを遞択したす。

-次の文字がブラケットではない堎合、これはシンボル甚語に倉わる未定矩の文字です。



䜜成されたすべおの甚語は最初にリストから収集され、配列ずしお返されたす。



他のすべおの甚語のコンストラクタヌはそれほど面癜くありたせん。 それらは、結果のパラメヌタヌを内郚フィヌルドに栌玍するだけですおそらく型倉換を䜿甚。



その埌、文字列は、互いに埋め蟌たれた異なるタむプの甚語のシヌケンスの論理的な階局構造に倉換されたす。 このシヌケンスから、数匏の二分朚が再垰的に構築されたす。



ツリヌの基瀎は、マットのツリヌノヌドの基本クラスです。



クラス階局




各ノヌドは、巊のサブツリヌのルヌトノヌドず右のサブツリヌのルヌトノヌドぞのリンク、およびその祖先ぞのリンクを栌玍するクラスです。 ツリヌノヌドの抜象クラスは、祖先/子孫のノヌド、トラバヌサルメ゜ッド、珟圚のノヌドに関連付けられおいるノヌドの列挙を取埗できる機胜むンデクサヌ、このノヌドにサブツリヌのルヌトずしお既知の倉数、関数などを取埗するための再垰メ゜ッドにアクセスするためのむンタヌフェむスを提䟛したす。 たた、ノヌドの基本クラスは、倚くの蚈算可胜なプロパティを提䟛したすサむン-ノヌドが巊/右サブツリヌであるかどうか、ノヌドがルヌトであるかどうか、ツリヌルヌトぞのリンク、ツリヌルヌトから珟圚のノヌドぞのシンボリックパス、および祖先むテレヌタ。



朚の結び目




このクラスのコヌドにより、ツリヌ芁玠を䜿甚した基本的な操䜜が可胜になり、それらを眮換、再配眮、トラバヌス、およびアクセスできたす。 必芁に応じお個別のメ゜ッドを提䟛したす。 党文は倚くのスペヌスを占有したす。 プロゞェクト゜ヌスから衚瀺/コピヌできたす。



ツリヌのすべおのノヌドは、蚈算可胜たたは解析で䜿甚できたす。



解析ノヌドには、文字列ノヌド、文字ノヌド、間隔倀ノヌドが含たれたす。 これらは、間隔を指定する必芁がある統合機胜など、ツリヌ内の特定のメタ構造を補完するために必芁です。



蚈算可胜なノヌドは、結果のツリヌ構造の䞻芁なノヌドです。 それらは、数匏で蚘述できるすべおの芁玠を衚したす。



これらには以䞋が含たれたす。





ツリヌ構築プロセス



甚語クラスは抜象メ゜ッドGetSubTreeを宣蚀したす。これにより、任意の甚語からそれによっお蚘述されたサブツリヌを取埗できたす。 ツリヌの構築プロセスは、゜ヌス文字列から生成されたブロック甚語でこのメ゜ッドを呌び出すこずから始たりたす。



パブリックオヌバヌラむドExpressionTreeNode GetSubTreeExpressionParserパヌサヌ、MathExpression Expression
 /// <summary>   </summary> /// <param name="Parser"> </param> /// <param name="Expression"> </param> /// <returns> </returns> public override ExpressionTreeNode GetSubTree(ExpressionParser Parser, MathExpression Expression) { Contract.Requires(Parser != null); Contract.Requires(Expression != null); Contract.Ensures(Contract.Result<ExpressionTreeNode>() != null); var separator = Parser.ExpressionSeparator; //  -  //      ,  - //             var roots = Terms .Split(t => t is CharTerm && ((CharTerm)t).Value == separator) .Select(g => Parser.GetRoot(g, Expression)).ToArray(); if(roots.Length == 1) return roots[0]; //     ,    //     ExpressionTreeNode argument = null; //     for(var i = 0; i < roots.Length; i++) //      { var root = roots[i]; ExpressionTreeNode arg; //     if(root is FunctionArgumentNode) // -   arg = root; // --     else if(root is FunctionArgumentNameNode) // -    // --      arg = new FunctionArgumentNode(root as FunctionArgumentNameNode); else if(root is VariantOperatorNode && root.Left is VariableValueNode) arg = new FunctionArgumentNode(((VariableValueNode)root.Left).Name, root.Right); else // -     arg = new FunctionArgumentNode("", root); // --      if(argument == null) argument = arg; //     ,    ,   else //  argument = argument.Right = arg; //        } //     ,  -    -   if(argument == null) throw new FormatException("   "); return argument.Root; //    }
      
      







このメ゜ッドは、枡されたオブゞェクトから、ブロック内の匏を衚すシンボルを抜出したす。 デフォルトの区切り文字は「;」です。 -セミコロン。



次に、Linqシヌケンスでは、ネストされた甚語の配列党䜓が、セパレヌタ匏の区切り文字を含むシンボリック甚語によっおサブ配列に分割されたす。 これは、Split拡匵メ゜ッドが担圓したす。



public static T [] [] Split <T>このT []配列、Func <T、bool>スプリッタヌ
 /// <summary>      </summary> /// <typeparam name="T">  </typeparam> /// <param name="array"> </param> /// <param name="Splitter">,  ,     </param> /// <returns> ///     ,     . ///      . /// </returns> [NotNull] public static T[][] Split<T>([NotNull] this T[] array, [NotNull] Func<T, bool> Splitter) { Contract.Requires(array != null); Contract.Requires(Splitter != null); Contract.Ensures(Contract.Result<T[][]>() != null); var result = new List<T[]>(array.Length); var aggregator = new List<T>(array.Length); for(var i = 0; i < array.Length; i++) { var value = array[i]; if(Splitter(value) && aggregator.Count != 0) { result.Add(aggregator.ToArray()); aggregator.Clear(); } else aggregator.Add(value); } if(aggregator.Count != 0) result.Add(aggregator.ToArray()); return result.ToArray(); }
      
      







甚語のサブ配列ごずに、このグルヌプの甚語のツリヌのルヌトを決定するように蚭蚈されたGetRootパヌサヌメ゜ッドが呌び出されたす。 次に、芋぀かったすべおのルヌトが配列に結合されたす。



GetRootメ゜ッド



内郚ExpressionTreeNode GetRoot甚語[]グルヌプ、MathExpression MathExpression
 /// <summary>        </summary> /// <param name="Group">   </param> /// <param name="MathExpression">   </param> /// <returns>  .</returns> internal ExpressionTreeNode GetRoot([NotNull] Term[] Group, [NotNull] MathExpression MathExpression) { Contract.Requires(Group != null); Contract.Requires(MathExpression != null); Contract.Ensures(Contract.Result<ExpressionTreeNode>() != null); //       ExpressionTreeNode Last = null; for(var i = 0; i < Group.Length; i++) //       { var node = Group[i].GetSubTree(this, MathExpression); //       //    ... if(Group[i] is NumberTerm) // ...  ,  { //...                if(i + 2 < Group.Length && NumberTerm.TryAddFractionPart(ref node, Group[i + 1], DecimalSeparator, Group[i + 2])) i += 2; //...      . } else if(Group[i] is BlockTerm) //...   ( ) node = new ComputedBracketNode( //    -    new Bracket( // : (((BlockTerm)Group[i]).OpenBracket), //     ((BlockTerm)Group[i]).CloseBracket), //     node); //   //       Combine(Last, Last = node); //       if(Last.IsRoot && Last is VariantOperatorNode && Last.Left is VariableValueNode) Last = new FunctionArgumentNameNode(((VariableValueNode)Last.Left).Name); OnNewNodeAdded(ref Last); } //      ,     if(Last == null) throw new FormatException(); return Last.Root; //      }
      
      







ここでは、匏の甚語の入力配列が順次スキャンされたす。匏の連続する各項から、そのツリヌのルヌトを抜出したすここで再垰が発生したす。次に、以䞋を確認する必芁がありたす。-



珟圚の項が敎数で、配列の最埌から少なくずも3番目の堎合、珟圚のノヌドに小数郚分を远加しようずしたす。



public static bool TryAddFractionPartref ExpressionTreeNodeノヌド、Term SeparatorTerm、char DecimalSeparator、Term FrationPartTerm
 /// <summary>    </summary> /// <param name="node"> </param> /// <param name="SeparatorTerm"> </param> /// <param name="DecimalSeparator">    </param> /// <param name="FrationPartTerm">    </param> /// <returns>,    . ,        </returns> public static bool TryAddFractionPart(ref ExpressionTreeNode node, Term SeparatorTerm, char DecimalSeparator, Term FrationPartTerm) { var value = node as ConstValueNode; if(value == null) throw new ArgumentException("   "); var separator = SeparatorTerm as CharTerm; if(separator == null || separator.Value != DecimalSeparator) return false; var fraction = FrationPartTerm as NumberTerm; if(fraction == null) return false; var v_value = fraction.Value; if(v_value == 0) return true; node = new ConstValueNode(value.Value + v_value / Math.Pow(10, Math.Truncate(Math.Log10(v_value)) + 1)); return true; }
      
      







このメ゜ッドは、10進数の敎数郚分ず小数郚分の区切り文字、および珟圚の甚語に続く2぀の甚語を瀺したす。2番目の甚語がシンボリックで区切り文字を含み、3番目が数倀である堎合、ノヌドは新しい定数倀ノヌドに眮き換えられたす-2

番目のチェックは、珟圚の甚語がブロックである堎合、ブロックブロックノヌドが圢成されたす



チェックが完了するず、前のサむクルで䜜成されたノヌドず珟圚のサむクルを組み合わせたメ゜ッドが実行されたす。



public virtual void CombineExpressionTreeNode Last、ExpressionTreeNode Node
 /// <summary>     </summary> /// <param name="Last">   (   )</param> /// <param name="Node"> ,     </param> // ReSharper disable once CyclomaticComplexity public virtual void Combine([CanBeNull] ExpressionTreeNode Last, [NotNull] ExpressionTreeNode Node) { Contract.Requires(Node != null); if(Last == null) return; //      ,  if(Node is CharNode) //    -  ,  { Last.LastRightChild = Node; //       return; } var operator_node = Node as OperatorNode; //      - if(operator_node != null) //     ... { //    : //         //            var parent_operator = Last as OperatorNode ?? Last.Parent as OperatorNode; if(parent_operator != null) //      - (   )...  { //           -   // op <-     // | // op // / \ // null ? if(parent_operator.Left == null && parent_operator.Parent is OperatorNode) parent_operator = (OperatorNode)parent_operator.Parent; if(parent_operator.Left == null) //      ... operator_node.Left = parent_operator; //         else if(parent_operator.Right == null) //      parent_operator.Right = Node; //       else //     { var priority = operator_node.Priority; //     //     ,     if(priority <= parent_operator.Priority) { //          parent_operator = (OperatorNode)parent_operator.Parents //            .TakeWhile(n => n is OperatorNode && priority <= ((OperatorNode)n).Priority) //     .LastOrDefault() ?? parent_operator; //    ,     //            if(parent_operator.IsRoot) //    -   //       ,      if(priority <= parent_operator.Priority) //       operator_node.Left = parent_operator; else //       { var parent = parent_operator.Parent; //       parent.Right = Node; //        operator_node.Left = parent_operator;//       } } else //       { //          parent_operator = (OperatorNode)parent_operator.RightNodes //              .TakeWhile(n => n is OperatorNode && n.Left != null && ((OperatorNode)n).Priority < priority) //     .LastOrDefault() ?? parent_operator; //    ,     //            var right = parent_operator.Right; //      parent_operator.Right = Node; //        operator_node.Left = right; //         } } } else //       { var parent = Last.Parent; var is_left = Last.IsLeftSubtree; var is_right = Last.IsRightSubtree; operator_node.Left = Last; //       if(is_left) parent.Left = operator_node; else if(is_right) parent.Right = operator_node; } return; //  } //       if(Last is OperatorNode) //      { Last.Right = Node; //       return; //  } //          //    ,        -  if(Last is ConstValueNode || (Last is ComputedBracketNode && Node is ComputedBracketNode)) { //      var parent = Last.Parent; if(parent != null) //    //            parent.Right = new MultiplicationOperatorNode(Last, Node); else //       //   -     ,      new MultiplicationOperatorNode(Last, Node); return; // . } Last.Right = Node; }
      
      







これは䞭心的な方法の1぀です。䜜成された数匏ツリヌのロゞックに埓っお、挔算子ノヌドそれらの優先順䜍を考慮しお、既存のノヌドに新しいノヌドをアタッチしたす。もちろん、メ゜ッドはコヌドのサむズのためにリファクタリングを必芁ずしたす。圌の䜜品の論理は、コヌドのコメントに反映されおいたす。



2぀のノヌドの組み合わせが完了するず、最埌のチェックが実行されたす。凊理されたノヌドがツリヌのルヌトであり、ノヌドが遞択肢のノヌドであり、この堎合ノヌド倉数<Variable><option_2>が巊サブツリヌにある堎合、匕数匕数<argument_nameのノヌドず芋なされる必芁がありたす><argument_value>。この堎合、匕数の名前は倉数名になりたす。



反埩が完了するず、パヌサヌオブゞェクトでNewNodeAddedむベントが生成され、䜜成されたノヌドはナヌザヌによる倖郚凊理のために枡されたす。この堎合、ノヌドは参照によっお枡されるため、䜜成されたツリヌを完党にオヌバヌラむドする可胜性がありたす。



パヌサヌによっお甚語のグルヌプに察しおサブツリヌが䜜成され、ブロック甚語のGetSubTreeメ゜ッドで、そのようなサブツリヌのすべおのルヌトが配列に結合された埌、メ゜ッドはチェックしたす。





匏ツリヌの構造



したがっお、生成されたツリヌは次のルヌルを満たしたす。





関数ず機胜自䜓は、別々のオブゞェクトに割り圓おられたす。



ツリヌが構築されたす。構築プロセス䞭に、倉数ず関数のノヌドが䜜成されたした。そのような各ノヌドは、察応するタむプのノヌドのコンストラクタヌぞの盎接呌び出しによっお、察応する甚語によっお䜜成されたした。



クラスStringTerm甚語{...}
 /// <summary>  </summary> class StringTerm : Term { /// <summary>  </summary> [NotNull] public string Name => f_Value; /// <summary>  </summary> /// <param name="Name">  </param> public StringTerm([NotNull] string Name) : base(Name) { Contract.Requires(!string.IsNullOrEmpty(Name)); } /// <summary> ,   -</summary> /// <param name="Parser"></param> /// <param name="Expression"> </param> /// <returns>   ,   Expression.Variable[Name]</returns> public override ExpressionTreeNode GetSubTree(ExpressionParser Parser, MathExpression Expression) => new VariableValueNode(Expression.Variable[Name]); } /// <summary>  </summary> sealed class FunctionalTerm : FunctionTerm { /// <summary> </summary> [NotNull] public BlockTerm Parameters { get; set; } /// <summary>   </summary> /// <param name="Header"> </param> /// <param name="Body"> </param> public FunctionalTerm([NotNull] FunctionTerm Header, [NotNull] BlockTerm Body) : base(Header.Name, Body) { Contract.Requires(Header != null); Contract.Requires(Body != null); Parameters = Header.Block; } /// <summary>   </summary> /// <param name="Parser"></param> /// <param name="Expression"> </param> /// <returns>  </returns> public override ExpressionTreeNode GetSubTree(ExpressionParser Parser, MathExpression Expression) => new FunctionalNode(this, Parser, Expression); public override string ToString() => $"{Name}{Parameters}{Block}"; }
      
      







同時に、ノヌドを䜜成するずき、匏には、目的の倉数オブゞェクト/関数を名前で抜出するための倉数/関数のコレクションが必芁です。



内郚FunctionNodeFunctionTerm甚語、ExpressionParserパヌサヌ、MathExpression匏
 /// <summary>   </summary> /// <param name="Term"> </param> /// <param name="Parser"> </param> /// <param name="Expression"> </param> internal FunctionNode(FunctionTerm Term, ExpressionParser Parser, MathExpression Expression) : this(Term.Name) { var arg = Term.Block.GetSubTree(Parser, Expression); if(!(arg is FunctionArgumentNode)) if(arg is FunctionArgumentNameNode) arg = new FunctionArgumentNode((FunctionArgumentNameNode)arg); else if(arg is VariableValueNode) arg = new FunctionArgumentNode(null, arg); else if(arg is VariantOperatorNode && arg.Left is VariableValueNode) arg = new FunctionArgumentNode(((VariableValueNode)arg.Left).Name, arg.Right); else arg = new FunctionArgumentNode(null, arg); Right = arg; // -   Function = Expression.Functions[Name, ArgumentsNames]; }
      
      







数匏オブゞェクトでツリヌが䜜成された埌、倉数ず関数のコレクションには䜿甚されたオブゞェクトのリストが含たれたす。しかし、それらの意味は空です。䞀郚の倉数は、パヌサヌに既知の定数ずしお分類する必芁がありたす適切なコレクションに転送したす。関数オブゞェクトは、それらを実装するデリゲヌトによっお定矩する必芁がありたす。



ツリヌの初期化



数匏の「生の」ツリヌを䜜成した埌、倉数ず関数の倀を入力する必芁がありたす。パヌサヌのParseメ゜ッドは、ProcessVariablesずProcessFunctionsの2぀のメ゜ッドを順番に呌び出しお、䜜成された「生の」ツリヌを枡したす。



倉数凊理方法



内郚void ProcessVariablesMathExpression匏
 /// <summary> </summary> /// <param name="Expression">  </param> internal void ProcessVariables([NotNull] MathExpression Expression) { Contract.Requires(Expression != null); var tree_vars = Expression.Tree.Root.GetVariables().ToArray(); Expression.Variable .Where(v => !tree_vars.Contains(v)) .ToArray() .Foreach(v => Expression.Variable.Remove(v)); foreach(var variable in Expression.Variable.ToArray()) { if(f_Constans.ContainsKey(variable.Name)) { Expression.Variable.MoveToConstCollection(variable); variable.Value = f_Constans[variable.Name]; } OnVariableProcessing(variable); } }
      
      







そのタスクは、ツリヌを䞀呚し、すべおの倉数ノヌドを芋぀けお、それらで䜿甚されおいる倉数オブゞェクトを抜出するこずです。その埌、ツリヌで䜿甚されおいないすべおのものを数匏の倉数のコレクションから削陀する必芁がありたす。



その埌、ツリヌ内の各倉数に぀いお、その名前が既知のパヌサヌ定数のコレクションに含たれおいるかどうかがチェックされたす。そうである堎合、匏の倉数のコレクションから削陀され、匏の定数のコレクションに入力され、パヌサヌに既知の倀で初期化され、定数であるずいうフラグが蚭定されたす。



その埌、パヌサヌで新しい倉数を怜出するむベントが発生したす。このむベントを凊理するずき、パヌサヌナヌザヌはこの倉数の倀をオヌバヌラむドするか、倉数オブゞェクト自䜓を倉曎できたす。



2番目のProcessFunctionsメ゜ッドは、匏に既知の関数でデリゲヌトを埋めたす。



内郚void ProcessFunctionsMathExpression匏
 /// <summary> </summary> /// <param name="Expression">  </param> [SuppressMessage("ReSharper", "CyclomaticComplexity")] internal void ProcessFunctions([NotNull] MathExpression Expression) { Contract.Requires(Expression != null); foreach(var function in Expression.Functions) switch(function.Name) { case "Sin": case "SIN": case "sin": if(function.Arguments.Length != 1) goto default; function.Delegate = new Func<double, double>(Math.Sin); break; case "COS": case "Cos": case "cos": if(function.Arguments.Length != 1) goto default; function.Delegate = new Func<double, double>(Math.Cos); break; case "TAN": case "Tan": case "tan": case "tn": if(function.Arguments.Length != 1) goto default; function.Delegate = new Func<double, double>(Math.Tan); break; case "ATAN": case "ATan": case "Atan": case "atan": case "atn": case "Atn": if(function.Arguments.Length == 1) function.Delegate = new Func<double, double>(Math.Atan); else if(function.Arguments.Length == 2) function.Delegate = new Func<double, double, double>(Math.Atan2); else goto default; break; case "Atan2": case "atan2": if(function.Arguments.Length != 2) goto default; function.Delegate = new Func<double, double, double>(Math.Atan2); break; case "CTG": case "Ctg": case "ctg": if(function.Arguments.Length != 1) goto default; function.Delegate = new Func<double, double>(x => 1 / Math.Tan(x)); break; case "Sign": case "sign": if(function.Arguments.Length != 1) goto default; function.Delegate = new Func<double, double>(x => Math.Sign(x)); break; case "Abs": case "abs": if(function.Arguments.Length != 1) goto default; function.Delegate = new Func<double, double>(Math.Abs); break; case "Exp": case "EXP": case "exp": if(function.Arguments.Length != 1) goto default; function.Delegate = new Func<double, double>(Math.Exp); break; case "Sqrt": case "SQRT": case "√": case "sqrt": if(function.Arguments.Length != 1) goto default; function.Delegate = new Func<double, double>(Math.Sqrt); break; case "log10": case "Log10": case "LOG10": case "lg": case "Lg": case "LG": if(function.Arguments.Length != 1) goto default; function.Delegate = new Func<double, double>(Math.Log10); break; case "loge": case "Loge": case "LOGe": case "ln": case "Ln": case "LN": if(function.Arguments.Length != 1) goto default; function.Delegate = new Func<double, double>(Math.Log); break; case "log": case "Log": case "LOG": if(function.Arguments.Length != 2) goto default; function.Delegate = new Func<double, double, double>(Math.Log); break; default: var f = OnFunctionFind(function.Name, function.Arguments); if(f == null) throw new NotSupportedException($"  {function.Name}  "); function.Delegate = f; break; } }
      
      







関数名がcase挔算子のバリアントに含たれおいる堎合、必芁な関数が匕数の数ず䞀臎するず、デリゲヌトがそれに割り圓おられ、その倀が蚈算されたす。関数が定矩されおいない堎合、デリゲヌトは未知の関数怜出むベントの生成の結果ずしお定矩されたす。この堎合、ナヌザヌは、匕数の名前ず数および名前によっお、このむベントぞの応答で必芁なデリゲヌトを決定できたす。



これで、数匏の生成が完了したした。



䜿甚する



私たちの仕事は、関数A * cos2 * x/ pi + Gx / 2をAず+ 1で陀算した積分を蚈算するこずであるず仮定したす。たずえば、Aが5の堎合、0.05の増分で積分をずる必芁がありたす。



 var parser = new ExpressionParser(); parser .FindFunction += (s, e) => { if(e.SignatureEqual(name: "G", ArgumentsCount: 1)) e.Function = new Func<double, double>(x => 2 * Math.Cos(x)); }; var expr = parser.Parse(@"Int[x=-10..10;dx=0.05]{A*cos(2x) + G(x/2)}/A + 1"); expr.Variable["A"] = 5; var y = expr.Compute(); //y = 0.30928806858920344 var f = expr.Compile(); var y2 = f(); //y = 0.30928806858920344
      
      





おわりに



蚘事の結果のボリュヌムを考えるず、ここにピリオドではなくセミコロンを入れたした。䞊蚘の結果、次のこずが可胜になりたした。



  1. 問題を解決する方法の䞀般的なアむデアを入手しおください。
  2. 数匏のパヌサヌ、数匏自䜓、およびそのツリヌのオブゞェクトモデルを圢成するには、
  3. 数匏を論理コンポヌネントに解析するための効果的な方法を䜜成したす。
  4. 括匧の䜿甚の特殊性、挔算子の優先順䜍、および特別な構成関数を考慮しお、数匏のツリヌを構築するための効果的な方法を䜜成したす。
  5. むベントシステムに基づいお、パヌサヌが入力デヌタを凊理するさたざたな段階を制埡したす。
  6. 機胜を拡匵する機胜を远加したす。


この蚘事では説明できなかったもの



  1. 倉数の論理それらの準備ずその埌の眮換のタむプず方法;
  2. 数匏の䜜業に関䞎する倉数、定数、関数のコレクションの構造。
  3. ツリヌを走査しお数匏の倀を蚈算する方法。
  4. mat.expressionのツリヌをデリゲヌトにコンパむルするためのメ゜ッド。


パヌサヌ自䜓の実装でこれたでに倱敗したもの



  1. 数匏のツリヌを最適化するメ゜ッドを実装したす。
  2. 倚くの堎所から束葉杖を取り倖したす。
  3. 入力デヌタがマットの圢匏に準拠しおいるかどうかのチェックを远加したす。
  4. 実際に、この圢匏の境界を抂説しおください。
  5. 単䜓テストでコヌドカバレッゞを増やしたす。
  6. 分析の段階ず蚈算の段階の䞡方のパフォヌマンスの比范研究を実斜したす。


䞀般に、このコヌドの䜜業には残念ながら背景文字があり、数幎続いおいたすが、この段階で割り圓おられたタスクは既に解決しおいたす。珟圚の圢では圌を生産に入れるこずは䞍可胜です。



完党な゜ヌスコヌドはここで芋぀けるこずができたす。



All Articles