中置式を後置記法に変換する


23

この非公開の質問のタイトルを見たとき、私はそれが面白いコードゴルフチャレンジのように見えると思いました。そのため、次のように紹介します。

チャレンジ:

算術式を考慮すると、プログラム、発現またはサブルーチン書き込みインフィックス表記法、等1 + 2、に同一の発現出力ポストフィックス表記、すなわち1 2 +

(注:1月前半に同様の課題が投稿されました。 ただし、この個別の課題を正当化するために2つのタスクの詳細が十分に異なっていると感じます。また、下のすべてを入力した後、他のスレッドにのみ気づいたので、むしろただ捨てるだけではありません。)

入力:

入力は、以下からなる有効中置演算式から成る番号(非負整数は一つ以上の桁のシーケンスとして表される)、バランスのとれた括弧グループ化された部分式を示すために、4つの中置バイナリ演算子 +-*および/。これらはどれも、無視する必要のある任意の数のスペース文字で区切ることができます(そして式全体を囲むことができます)。1

正式な文法が好きな人のために、有効な入力を定義する簡単なBNFのような文法を以下に示します。簡潔にするため、文法にはオプションのスペースは含まれていません。スペースは、2つのトークン(数字の中の数字以外)の間にある場合があります。

expression     := number | subexpression | expression operator expression
subexpression  := "(" expression ")"
operator       := "+" | "-" | "*" | "/"
number         := digit | digit number
digit          := "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"

1スペースの存在が解析に影響を与える可能性がある唯一のケースは、2つの連続した数字を区切る場合です。ただし、演​​算子で区切られていない2つの数値は有効な中置式では発生しないため、このケースは有効な入力では発生しません。

出力:

出力は、入力と同等の後置式でなければなりません。出力式は、次の文法(スペースを含む)2のように、数字と演算子のみで構成され、隣接するトークンの各ペア間に単一のスペース文字含まれている必要があります。

expression  := number | expression sp expression sp operator
operator    := "+" | "-" | "*" | "/"
number      := digit | digit number
digit       := "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
sp          := " "

2簡単にするために、numberこの文法の生成では、以下の規則によって出力が禁止されている場合でも、先頭にゼロを付けて数字を認めています。

演算子の優先順位:

括弧がない場合、次の優先順位規則が適用されます。

  • 演算子*および/は、+およびより優先順位が高くなり-ます。
  • 演算子*/は、互いに同等の優先順位を持っています。
  • 演算子+-は、互いに同等の優先順位を持っています。
  • すべての演算子は左結合です。

たとえば、次の2つの式は同等です。

1 + 2 / 3 * 4 - 5 + 6 * 7
((1 + ((2 / 3) * 4)) - 5) + (6 * 7)

そして、両方とも次の出力を生成するはずです。

1 2 3 / 4 * + 5 - 6 7 * +

(これらは、同じ優先順位のルールは、C言語のように、そこから派生ほとんどの言語である。彼らはおそらく、あなたが小学校で教えられたルールに似ているの相対的な優先のため、おそらく除く外*/。)

その他の規則:

  • 与えられた解が式またはサブルーチンである場合、入力が提供され、出力が単一の文字列として返される必要があります。ソリューションが完全なプログラムである場合、標準入力から中置式を含む行を読み取り、後置バージョンを含む行を標準出力に出力する必要があります。

  • 入力の数字に、先行ゼロ含まれる場合があります。出力の数字には先行ゼロがあってはなりません(数字0を除き、これはとして出力されます0)。

  • 何らかの方法で式を評価または最適化することは期待されていません。特に、演算子が連想的、可換的、または他の代数的アイデンティティを必ずしも満たすと仮定すべきではありません。つまり、あなたがその例が想定するべきではない1 + 2と等しい2 + 1か、ということ1 + (2 + 3)イコール(1 + 2) + 3

  • あなたは、入力中の数字は2超えていないことを仮定してもよい31 = 2147483647 1を- 。

これらのルールは、正しい出力が入力によって一意に定義されるようにすることを目的としています。

例:

以下に、いくつかの有効な入力式と対応する出力をフォームで示します"input" -> "output"

"1"                  ->  "1"
"1 + 2"              ->  "1 2 +"
" 001  +  02 "       ->  "1 2 +"
"(((((1))) + (2)))"  ->  "1 2 +"
"1+2"                ->  "1 2 +"
"1 + 2 + 3"          ->  "1 2 + 3 +"
"1 + (2 + 3)"        ->  "1 2 3 + +"
"1 + 2 * 3"          ->  "1 2 3 * +"
"1 / 2 * 3"          ->  "1 2 / 3 *"
"0102 + 0000"        ->  "102 0 +"
"0-1+(2-3)*4-5*(6-(7+8)/9+10)" -> "0 1 - 2 3 - 4 * + 5 6 7 8 + 9 / - 10 + * -"

(少なくとも、これらすべてが正しいことを願っています。手作業で変換を行ったので、間違いが入り込んだかもしれません。)

明確にするために、次の入力はすべて無効です。それらが与えられた場合、ソリューションが何をするか問題ではありませ(もちろん、たとえば、エラーメッセージを返す方が、無限のメモリを消費するよりもいいです):

""
"x"
"1 2"
"1 + + 2"
"-1"
"3.141592653589793"
"10,000,000,001"
"(1 + 2"
"(1 + 2)) * (3 / (4)"

Lispのような表記法は受け入れられますか?たとえば、1 2 3 4 +「1 + 2 + 3 + 4」を意味します。
-Hauleth

3
@Hauleth:この挑戦ではありません。また、括弧なしで、どのように解析し1 2 3 4 + *ますか?
イルマリカロネン

では、出力で末尾の空白(改行を含む)は許可されませんか?
ブレッドボックス

@breadbox:末尾の改行は問題ありません。実際、末尾の空白は許可されることを明示的に明確にします。
イルマリカロネン

最後の有効な例に対して「0 1-2 3-4 * 5 6 7 8 + 9 /-10 + *-+」を出力するソリューションがありますが、これは私には正しいようです。確認できますか?(最後の+演算子に注意してください)
coredump

回答:


8

シェルユーティリティ-60文字

bc -c|sed -re's/[@iK:Wr]+/ /g;s/[^0-9]/ &/g;s/ +/ /g;s/^ //'

さまざまな問題を修正しましたが、かなり長くなりました:(


1
これはかなり賢いですが、9を超える数を正しく処理できないようです。
ブレッドボックス

@ breadbox、sed -re's/[:@iKWr]+/ /g'1文字のコストで修正します。
ウゴレン

おっと、連続した演算子の間にスペースがないため、@ ugorenの提案は機能しません。私もそれを修正する必要があります
ジェフリーディ

4

C、250 245 236 193 185文字

char*p,b[99];f(char*s){int t=0;for(;*p-32?
*p>47?printf("%d ",strtol(p,&p,10)):*p==40?f(p++),++p:
t&&s[t]%5==2|*p%5-2?printf("%c ",s[t--]):*p>41?s[++t]=*p++:0:++p;);}
main(){f(p=gets(b));}

これは、基本的なロジックをまだ反映している、読めないソースの読みやすいバージョンです。それは実際にはかなり簡単なプログラムです。それがしなければならない唯一の実際の作業は、高結合性演算子に出会ったときに低結合性演算子をスタックにプッシュし、その部分式の「終わり」でそれをポップバックすることです。

#include <stdio.h>
#include <stdlib.h>

static char buf[256], stack[256];
static char *p = buf;

static char *fix(char *ops)
{
    int sp = 0;

    for ( ; *p && *p != '\n' && *p != ')' ; ++p) {
        if (*p == ' ') {
            continue;
        } else if (*p >= '0') {
            printf("%ld ", strtol(p, &p, 10));
            --p;
        } else if (*p == '(') {
            ++p;
            fix(ops + sp);
        } else {
            while (sp) {
                if ((ops[sp] == '+' || ops[sp] == '-') &&
                        (*p == '*' || *p == '/')) {
                    break;
                } else {
                    printf("%c ", ops[sp--]);
                }
            }
            ops[++sp] = *p;
        }
    }
    while (sp)
        printf("%c ", ops[sp--]);
    return p;
}

int main(void)
{
    fgets(buf, sizeof buf, stdin);
    fix(stack);
    return 0;
}

を削除して文字を保存しますif。例if(!*p||*p==41)return p;s[++t]=*p;}->return*p&&*p-41?s[++t]=*p:p;
ugoren

K&Rスタイルの宣言:*f(p,s)char*p,s;{
ugoren

1. ifテストが失敗した場合に返すのはエラーです。2.知っていますが、K&R関数declsは線を引く場所です。私は彼らに戻ることができません。
ブレッドボックス

とにかく関数の終わりに戻ると思った。不在}}for。しかし、ここに改善があります:printf(" %ld"+!a,...
ugoren

1
また、pグローバルにする必要があると思います(再帰呼び出しは、呼び出し先を呼び出しp元に割り当てるだけです)。それからf(p=gets(b))
ウゴレン

2

Bash w / Haskell w / Cプリプロセッサ sed、180 195 198 275

echo 'CNumO+O-O*fromInteger=show
CFractionalO/
main=putStr$'$*|sed 's/C\([^O]*\)/instance \1 String where /g
s/O\(.\?\)/a\1b=unwords\[a,b,\"\1\"];/g'|runghc -XFlexibleInstances 2>w

ついに、Cソリューションよりも長くなくなりました。Haskellの重要な部分は、bcソリューションとほぼ同じくらい怠zyです...

入力をコマンドラインパラメーターとして受け取ります。wこの変更が気に入らない場合、いくつかのghc警告メッセージを含むファイルが作成されますrunghc 2>/dev/null


1
バスク?(Bas h + H aske ll + s ed
CalculatorFeline

2

パイソン2、290の 272 268 250 243 238バイト

これでようやくJSの回答よりも短くなりました!

これは、シャンティングヤードアルゴリズムの基本的な実装を使用する完全なプログラムです。入力は引用符付きの文字列として与えられ、結果はに出力されSTDOUTます。

import re
O=[];B=[]
for t in re.findall('\d+|\S',input()):exec("O=[t]+O","i=O.index('(');B+=O[:i];O=O[i+1:]","while O and'('<O[0]and(t in'*/')<=(O[0]in'*/'):B+=O.pop(0)\nO=[t]+O","B+=`int(t)`,")[(t>'/')+(t>')')+(t>'(')]
print' '.join(B+O)

オンラインでお試しください!


説明:

最初に行う必要があるのは、入力をトークンに変換することです。これを行うには、\d+|\S「数字の任意のグループとスペース以外の文字」に大まかに変換された正規表現のすべての一致を検索します。これにより、空白が削除され、隣接する数字が単一のトークンとして解析され、演算子が個別に解析されます。

シャンティングヤードアルゴリズムの場合、処理する必要がある4つの異なるトークンタイプがあります。

  • ( -左括弧
  • ) -右括弧
  • +-*/ -オペレーター
  • 9876543210 -数値リテラル

ありがたいことに、これらのASCIIコードはすべて示された順序でグループ化されているため、式(t>'/')+(t>')')+(t>'(')を使用してトークンタイプを計算できます。その結果、数字は3、演算子は2、右括弧は1、左括弧は0になります。

これらの値を使用execして、トークンタイプに基づいて、実行する対応するスニペットを取得するために、後で大きなタプルにインデックスを付けます。これはトークンごとに異なり、シャンティングヤードアルゴリズムのバックボーンです。2つのリストが(スタックとして)使用されます:(O操作スタック)とB(出力バッファー)。すべてのトークンが実行された後、Oスタック上の残りの演算子が出力バッファーと連結され、結果が出力されます。


2

プロローグ(SWI-プロローグ)、113バイト

c(Z,Q):-Z=..[A,B,C],c(B,S),c(C,T),concat_atom([S,T,A],' ',Q);term_to_atom(Z,Q).
p(X,Q):-term_to_atom(Z,X),c(Z,Q).

オンラインでお試しください!

SWI Prologには、GNU Prologよりもはるかに優れたビルトインのセットがありますが、Prologの構文の冗長性により、まだ多少抑制されています。

説明

term_to_atom逆方向に実行すると、中置表記法(アトムとして格納されている)を解析ツリーに解析します(通常の優先規則に従い、先行ゼロと空白を削除します)。次に、ヘルパー述語cを使用して、解析ツリー上で構造再帰を実行し、深さ優先の方法で後置記法に変換します。


1

Javascript(ES6)、244バイト

f=(s,o={'+':1,'-':1,'*':2,'/':2},a=[],p='',g=c=>o[l=a.pop()]>=o[c]?g(c,p+=l+' '):a.push(l||'',c))=>(s.match(/[)(+*/-]|\d+/g).map(c=>o[c]?g(c):(c==')'?eval(`for(;(i=a.pop())&&i!='(';)p+=i+' '`):c=='('?a.push(c):p+=+c+' ')),p+a.reverse().join` `)

例:
呼び出し:f('0-1+(2-3)*4-5*(6-(7+8)/9+10)')
出力:(0 1 - 2 3 - 4 * + 5 6 7 8 + 9 / - 10 + * -末尾のスペースを使用)

説明:

f=(s,                                                     //Input string
    o={'+':1,'-':1,'*':2,'/':2},                          //Object used to compare precedence between operators
    a=[],                                                 //Array used to stack operators
    p='',                                                 //String used to store the result
    g=c=>                                                 //Function to manage operator stack
        o[l=a.pop()]>=o[c]?                               //  If the last stacked operator has the same or higher precedence
            g(c,p+=l+' '):                                //  Then adds it to the result and call g(c) again
            a.push(l||'',c)                               //  Else restack the last operator and adds the current one, ends the recursion.
)=>                                                       
    (s.match(/[)(+*/-]|\d+/g)                             //Getting all operands and operators
    .map(c=>                                              //for each operands or operators
        o[c]?                                             //If it's an operator defined in the object o
            g(c)                                          //Then manage the stack
            :(c==')'?                                     //Else if it's a closing parenthese
                eval(`                                    //Then
                    for(;(i=a.pop())&&i!='(';)            //  Until it's an opening parenthese
                        p+=i+' '                          //  Adds the last operator to the result
                `)                                        
                :c=='('?                                  //Else if it's an opening parenthese
                    a.push(c)                             //Then push it on the stack
                    :p+=+c+' '                            //Else it's an operand: adds it to the result (+c removes the leading 0s)
        )                                                 
    )                                                     
    ,p+a.reverse().join` `)                               //Adds the last operators on the stack to get the final result

1

R、142バイト

Rは自分自身を解析できるため、ホイールを再発明するのではなく、パーサーを機能させてプレフィックス表記を出力し、再帰関数を使用して後置表記に切り替えます。

f=function(x,p=1){
if(p)x=match.call()[[2]]
if((l=length(x))>1){
f(x[[2]],0)
if(l>2)f(x[[3]],0)
if((z=x[[1]])!="(")cat(z,"")
}else cat(x,"")
}

p引数は非標準の評価(どこでもRプログラマの悩みの種)の使用を制御することで、いくつかの余分があるif(私たちは避けたい)、ブラケットの出力を制御するために、そこにsが。

入力: (0-1+(2-3)*4-5*(6-(7+8)/9+10))

出力: 0 1 - 2 3 - 4 * + 5 6 7 8 + 9 / - 10 + * -

入力: (((((1))) + (2)))

出力: 1 2 +

おまけとして、任意のシンボル、および最大2つの引数を持つ定義済みの関数で動作します。

オイラーのアイデンティティ

入力: e^(i*pi)-1

出力: e i pi * ^ 1 -

1から100の間の13の配当

入力: which(1:100 %% 13 == 0)

出力: 1 100 : 13 %% 0 == which

時間の関数としてのひな鶏の体重の線形回帰

入力: summary(lm(weight~Time, data=ChickWeight))

出力: weight Time ~ ChickWeight lm summary

最後の例はおそらくOPの範囲外ですが、接尾辞表記法を使用しているため、...

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