シャンティングヤードアルゴリズムにおける関数の優先順位


9

ウィキペディアで説明されているように、シャンティングヤードアルゴリズムを使用しています

演算子を処理するときのアルゴリズムの説明は次のとおりです。

トークンが演算子o1の場合:

演算子スタックの一番上に演算子トークンo2があり、

o1 is left-associative and its precedence is less than or equal to
that of o2, or

o1 is right associative, and has precedence less than that of o2,

次に、オペレータースタックから出力キューにo2をポップします。

o1をオペレータースタックにプッシュします。

ただし、次の例を示します。

入力: sin max 2 3 / 3 * 3.1415

アルゴリズムが/トークンをヒットした場合、何が起こるかについての説明は次のとおりです。

Token |        Action       |   Output (in RPN) |   Operator Stack
...
/     | Pop token to output | 2 3 max           | / sin 
...

彼らは関数トークンをmaxから取り出してstackに入れていqueueます。彼らのアルゴリズムによると、これは関数トークンが両方とも演算子であり、演算子よりも優先順位が低いことを意味するように思われます/

これが事実であるかどうかについての説明はありません。では、Shunting-yardアルゴリズムの場合、関数の優先順位は何ですか?機能は右または左に関連付けられていますか?それともウィキペディアは単に不完全/不正確ですか?

回答:


5

直接的な答えは、関数は演算子ではないということです。リンクしたページから:

トークンが関数トークンの場合、それをスタックにプッシュします。

関数のケース(接頭辞から接尾辞へ)は演算子のケース(接頭辞から接尾辞へ)よりもはるかに単純であるため、これは言う必要があるすべてです。

フォローアップの質問の場合:優先順位と結合性の概念が必要なのは、複数の中置演算子を持つ式の継承のあいまいさのためです。関数トークンはすでにプレフィックス表記を使用しているため、問題はありません。あなたはかどうかを知る必要はありませんsinか、maxということを把握するために「より高い優先度を」持っているmaxニーズが最初に評価されます。トークンの順序からはすでに明らかです。これが、コンピュータがpre / postfix表記を最初に好む理由であり、infixをpre / postfixに変換するためのこのアルゴリズムがある理由です。

括弧が存在しない場合に関数の引数がどこで開始および終了するかについて、何らかのルールが必要です。そのため、関数は演算子よりも「優先される」、またはその逆であると言えます。しかし、中置演算子とは異なり、すべての関数に対して単一の一貫した規則があれば、それらの構成を完全に明確にすることができます。


彼らのアルゴリズムは正しいです。間違っているのは彼らの例です。インフィックス表記法は、機能をラップ括弧を含むべきである:sin( max( 2 3) / 3 * 3.1415)
MirroredFate

間違って呼び出すかどうかはわかりませんが、これは、すべての関数呼び出しの前後に括弧とコンマが必要な言語を支持する強力な議論です。
Ixrec

彼らが説明しているアルゴリズムを使用してインフィックスを解析することは不可能であるため、それは正しくないと思います。
MirroredFate 2015

@Ixrec「トークンが関数トークンの場合は、スタックにプッシュする」という行が表示されません。ウィキペディアのページ。現在編集されている可能性があります。しかし、アルゴリズムで数値と同じように関数を処理できるということですか?
Abhinav 2018年

3

言語構文に応じて、考慮すべき2つの異なるケースがあります。言語が括弧を使用して関数の適用を示す場合(例f(2+1):)、優先順位は関係ありません。関数はスタックにプッシュされ、後にポップアウトされる必要があります(上記の例の場合、結果はです2 1 + f)。または、関数を値として扱い、すぐに出力し、閉じ括弧の後に関数呼び出し操作を出力することもできます(それ以外の場合は他の括弧と同じように扱う必要があります)。たとえばf 2 1 + $$は関数呼び出し操作です。

ただし、言語が括弧を使用して関数の呼び出しを示していないf 2 + 1場合は、Wikipediaの例のように、特別な句読点(例:)なしで関数の直後に引数を配置すると、状況は少し複雑になります。先ほど例で示した式があいまいであることに注意してください。fは2と1に適用され、結果に追加されますか、それとも2と1を一緒に追加してから、結果でfを呼び出しますか?

ここでも、2つの方法があります。関数に遭遇したときに演算子スタックにプッシュするだけで、必要な優先順位を割り当てることができます。これは最も単純なアプローチであり、引用された例が明らかに行ったことです。ただし、実際的な問題があります。まず、関数をどのように特定しますか?有限のセットがある場合は簡単ですが、ユーザー定義の関数がある場合、これはパーサーが環境にフィードバックしすぎる必要があることを意味します。また、複数の引数を持つ関数をどのように処理しますか?

この構文のスタイルでは、関数アプリケーションの演算子で便利な値として関数を使用する方がはるかに理にかなっていると感じています。次に、値を読み取るたびにアプリケーションオペレーターを挿入するだけで、最後に読み取ったものが値でもあったため、どの識別子が関数であるかを通知する特別な方法は必要ありません。関数を返す式を操作することもできます(関数としての操作スタイルでは困難または不可能)。これは、カリー化を使用して複数の引数関数を処理できることを意味します。これは、それらを直接処理することを大幅に簡略化したものです。

次に決定する必要があるのは、関数適用の優先順位だけです。選択はあなた次第ですが、このように機能する私が使用したすべての言語で、これはその言語で最も強力な結合演算子であり、正しい連想演算子でした。(Haskellの唯一の興味深いバリエーションは、強い結合バージョンが記述されているだけでなく$、その言語の同義語でもあります。これは、言語で最も弱い結合演算子である記号を使用して、f 2 + 1fを2に適用して適用することを可能にf $ 2 + 1します。残りの表現全体に)


3

Dijkstraの元の考え(Algol 60コンパイラペーパーの7〜11ページ、https://ir.cwi.nl/pub/9251)を読んだ後、要求された「シャントヤードの機能」を要求し、堅牢なソリューションが必要になった後、次のことを行いました:

解析:

  • 関数記述子をプッシュする
  • 部分式の開始括弧と同じように、引数の開始の左角括弧 "["をプッシュします。
  • 入力から "("から ")"のバランスの取れた引数リストシーケンスを読み取ります
  • これを出力トークンストリームにプッシュする
  • 引数の終わりの右角かっこ[]」を、彼の「補正右角かっこ」と同じようにプッシュします

Infix-to-postfix(回避ヤード):

  • 演算子スタックと同じように、別のスタック、関数スタックを追加します
  • 関数名をスキャンするときに、関数情報を関数スタックにプッシュします
  • 引数の終わりの右角括弧が表示されたら、関数スタックをポップして出力します

堅牢なテストや複雑なシナリオで完全に機能します。私のアプリケーション(コマンドライン引数を含む式エキスパンダー)では、複数引数関数とコンマ "、"トークンをサポートしてそれらを分離し、これらがプロセス全体を流れます。

例は "sqrt(3 ^ 2 + 4 ^ 2)"のようになり、 "3 2 ^ 4 2 ^ + sqrt"となり、最終的に "5"はプログラムが引数と見なします。bignumなので、 "" binomial(64、32)/ gcd(binomial(64、32)、binomial(63、31))) "==> big things ==>" 2 "が役に立ちます。" 123456 ^ 789 "は40,173桁で、タイミングは私のMacBookProで「評価= 0.000390秒」と非常に速く表示されます。

また、これを使用してテーブルのデータを展開し、それが便利だと思います。とにかく、これは、関数呼び出し、複数の引数、およびダイクストラのシャントヤードコンテキストでの深い入れ子を慎重に処理するための私のヒントです。今日は独立した考えからそれをしました。より良い方法があるかどうかわからない。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.