Chuyển đổi biểu thức infix thành ký hiệu postfix


23

Khi tôi nhìn thấy tiêu đề của câu hỏi đóng này , tôi nghĩ rằng nó trông giống như một thử thách golf mã thú vị. Vì vậy, hãy để tôi trình bày nó như vậy:

Thử thách:

Viết chương trình, biểu thức hoặc chương trình con, được đưa ra một biểu thức số học trong ký hiệu infix , như 1 + 2, đưa ra biểu thức tương tự trong ký hiệu hậu tố , nghĩa là 1 2 +.

(Lưu ý: Một thử thách tương tự đã được đăng vào đầu tháng 1. Tuy nhiên, tôi cảm thấy hai nhiệm vụ đủ khác nhau về chi tiết để biện minh cho thử thách riêng biệt này. Ngoài ra, tôi chỉ chú ý đến chủ đề khác sau khi gõ mọi thứ bên dưới, và tôi thích không chỉ vứt bỏ tất cả.)

Đầu vào:

Các đầu vào bao gồm một biểu thức số học ghi vào hợp lệ bao gồm số (số nguyên không âm biểu diễn dưới dạng chuỗi của một hoặc thập phân hơn chữ số), sự cân ngoặc để chỉ ra một subexpression nhóm, và bốn trung tố nhị phân các nhà khai thác + , -, */. Bất kỳ trong số này có thể được phân tách (và toàn bộ biểu thức được bao quanh) bởi một số ký tự khoảng trắng tùy ý, cần được bỏ qua. 1

Đối với những người thích ngữ pháp chính thức, đây là một ngữ pháp đơn giản giống như BNF xác định các đầu vào hợp lệ. Để đơn giản và rõ ràng, ngữ pháp không bao gồm các khoảng trắng tùy chọn, có thể xảy ra giữa hai mã thông báo bất kỳ (trừ các chữ số trong một số):

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 Trường hợp duy nhất mà sự hiện diện của không gian có thể ảnh hưởng đến phân tích cú pháp là khi chúng tách hai số liên tiếp; tuy nhiên, vì hai số không được phân tách bởi một toán tử có thể xảy ra trong biểu thức trung tố hợp lệ, trường hợp này không bao giờ có thể xảy ra trong đầu vào hợp lệ.

Đầu ra:

Đầu ra phải là một biểu thức postfix tương đương với đầu vào. Biểu thức đầu ra nên bao gồm chỉ số và các nhà khai thác, với một nhân vật không gian duy nhất giữa mỗi cặp thẻ lân cận, như trong ngữ pháp sau (mà không bao gồm các không gian) 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 Một lần nữa để đơn giản, việc numbersản xuất trong ngữ pháp này thừa nhận các số có số 0 đứng đầu, mặc dù chúng bị cấm trong đầu ra theo các quy tắc dưới đây.

Ưu tiên điều hành:

Trong trường hợp không có dấu ngoặc đơn, các quy tắc ưu tiên sau sẽ được áp dụng:

  • Các toán tử */có quyền ưu tiên cao hơn +-.
  • Các toán tử */có quyền ưu tiên như nhau.
  • Các toán tử +-có quyền ưu tiên như nhau.
  • Tất cả các nhà khai thác là liên kết trái.

Ví dụ: hai biểu thức sau là tương đương:

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

và cả hai sẽ mang lại đầu ra sau:

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

(Đây là các quy tắc ưu tiên giống như trong ngôn ngữ C và trong hầu hết các ngôn ngữ có nguồn gốc từ nó. Chúng có thể giống với các quy tắc bạn được dạy ở trường tiểu học, ngoại trừ có thể có quyền ưu tiên tương đối */.)

Quy tắc khác:

  • Nếu giải pháp đưa ra là một biểu thức hoặc chương trình con, đầu vào sẽ được cung cấp và đầu ra được trả về dưới dạng một chuỗi. Nếu giải pháp là một chương trình hoàn chỉnh, nó sẽ đọc một dòng chứa biểu thức infix từ đầu vào tiêu chuẩn và in một dòng chứa phiên bản postfix sang đầu ra tiêu chuẩn.

  • Các số trong đầu vào có thể bao gồm các số 0 đứng đầu. Các số trong đầu ra không được có các số 0 đứng đầu (ngoại trừ số 0, sẽ là đầu ra dưới dạng 0).

  • Bạn không cần phải đánh giá hoặc tối ưu hóa biểu thức theo bất kỳ cách nào. Cụ thể, bạn không nên cho rằng các toán tử nhất thiết phải thỏa mãn bất kỳ danh tính liên kết, giao hoán hoặc đại số nào khác. Đó là, bạn không nên cho rằng ví dụ 1 + 2bằng 2 + 1hoặc 1 + (2 + 3)bằng (1 + 2) + 3.

  • Bạn có thể giả sử rằng các số trong đầu vào không vượt quá 2 31 - 1 = 2147483647.

Các quy tắc này nhằm đảm bảo rằng đầu ra chính xác được xác định duy nhất bởi đầu vào.

Ví dụ:

Dưới đây là một số biểu thức đầu vào hợp lệ và các đầu ra tương ứng, được trình bày dưới dạng "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 + * -"

(Ít nhất, tôi hy vọng tất cả những điều này là chính xác; tôi đã thực hiện chuyển đổi bằng tay, vì vậy những sai lầm có thể đã xuất hiện.)

Để rõ ràng, các đầu vào sau đây đều không hợp lệ; nó không có vấn đề gì giải pháp của bạn nếu cho họ (mặc dù, tất nhiên, ví dụ như trả lại một thông báo lỗi là đẹp hơn, chẳng hạn, tiêu thụ một số lượng vô hạn của bộ nhớ):

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

Là Lisp như ký hiệu được chấp nhận? Ví dụ: 1 2 3 4 +nghĩa là `1 + 2 + 3 + 4`.
Hauleth

3
@Hauleth: Không phải trong thử thách này, không. Bên cạnh đó, không có dấu ngoặc đơn, bạn sẽ phân tích 1 2 3 4 + *như thế nào?
Ilmari Karonen

Vì vậy, không có khoảng trắng theo dõi (bao gồm một dòng mới) được cho phép trong otuput?
hộp bánh mì

@breadbox: Trailing newlines là OK. Trong thực tế, hãy để tôi làm rõ một cách rõ ràng rằng bất kỳ khoảng trắng dấu vết nào đều được cho phép.
Ilmari Karonen

Tôi có một giải pháp cho ra "0 1 - 2 3 - 4 * 5 6 7 8 + 9 / - 10 + * - +" cho ví dụ hợp lệ cuối cùng, có vẻ đúng với tôi. Bạn có thể kiểm tra? (Lưu ý toán tử + cuối cùng)
coredump

Câu trả lời:


8

Đồ dùng vỏ sò - 60 ký tự

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

Đã sửa các vấn đề khác nhau, nhưng nó đã lâu hơn rất nhiều :(


1
Điều này khá thông minh, ngoại trừ việc nó dường như không xử lý các số lớn hơn 9 một cách chính xác.
hộp bánh mì

@breadbox, sed -re's/[:@iKWr]+/ /g'sửa nó với chi phí 1 ký tự.
ugoren

Rất tiếc, mặc dù đề xuất @ugoren không hoạt động do các toán tử liên tiếp không còn khoảng cách giữa chúng; Tôi cũng phải đưa ra cách khắc phục cho điều đó
Geoff Reedy

4

C, 250 245 236 193 185 ký tự

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));}

Đây là phiên bản có thể đọc được của nguồn không được cung cấp, vẫn phản ánh logic cơ bản. Đây thực sự là một chương trình khá đơn giản. Công việc thực sự duy nhất mà nó phải làm là đẩy một toán tử có tính kết hợp thấp lên một ngăn xếp khi gặp phải một toán tử có tính kết hợp cao, và sau đó bật lại ở "phần cuối" của biểu hiện phụ đó.

#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;
}

Lưu ký tự bằng cách loại bỏ if. Ví dụ: if(!*p||*p==41)return p;s[++t]=*p;}>return*p&&*p-41?s[++t]=*p:p;
ugoren

Tuyên bố phong cách K & R:*f(p,s)char*p,s;{
ugoren

1. Đó là một lỗi để trở lại nếu ifthử nghiệm thất bại. 2. Tôi biết, nhưng chức năng K & R suy giảm là nơi tôi vẽ đường thẳng. Tôi không thể quay lại với họ.
hộp bánh mì

Tôi nghĩ rằng sự trở lại là ở cuối chức năng anyway. Bỏ lỡ }}for. Nhưng đây là một sự phê chuẩn:printf(" %ld"+!a,...
ugoren

1
Ngoài ra tôi nghĩ bạn nên thực hiện ptoàn cầu (cuộc gọi đệ quy chỉ gán plại niềm vui cho người gọi). Rồi làm f(p=gets(b)).
ugoren

2

Bash w / Haskell w / Tiền xử lý 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

Cuối cùng, nó không dài hơn giải pháp C nữa. Phần quan trọng của Haskell cũng lười biếng như giải pháp bc ...

Lấy đầu vào là tham số dòng lệnh. Một tệp wcó một số thông báo cảnh báo ghc sẽ được tạo, nếu bạn không thích thay đổi này runghc 2>/dev/null.


1
Đắm mình? ( Bas h + H aske ll + s ed )
Máy

2

Python 2, 290 272 268 250 243 238 byte

Bây giờ cuối cùng ngắn hơn câu trả lời JS!

Đây là một chương trình đầy đủ sử dụng một triển khai cơ bản của thuật toán sân shunting . Đầu vào được đưa ra dưới dạng một chuỗi trích dẫn và kết quả được in ra 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)

Hãy thử trực tuyến!


Giải trình:

Điều đầu tiên chúng ta cần làm là chuyển đổi đầu vào thành mã thông báo. Chúng tôi thực hiện điều này bằng cách tìm tất cả các kết quả khớp của regex \d+|\S, tạm dịch là "bất kỳ nhóm chữ số nào và bất kỳ ký tự không phải dấu cách nào". Điều này loại bỏ khoảng trắng, phân tích các chữ số liền kề dưới dạng các mã thông báo duy nhất và phân tích các toán tử riêng biệt.

Đối với thuật toán sân shunting, có 4 loại mã thông báo riêng biệt chúng ta cần xử lý:

  • ( - Dấu ngoặc trái
  • ) - Dấu ngoặc phải
  • +-*/ - Người vận hành
  • 9876543210 - Chữ số

Rất may, tất cả các mã ASCII của các mã này đều được nhóm theo thứ tự hiển thị, vì vậy chúng tôi có thể sử dụng biểu thức (t>'/')+(t>')')+(t>'(')để tính toán loại mã thông báo. Điều này dẫn đến 3 cho các chữ số, 2 cho các toán tử, 1 cho dấu ngoặc đơn bên phải và 0 cho dấu ngoặc đơn bên trái.

Sử dụng các giá trị này, chúng tôi lập chỉ mục vào bộ dữ liệu lớn sau execđể có được đoạn mã tương ứng để thực thi, dựa trên loại mã thông báo. Điều này là khác nhau đối với mỗi mã thông báo và là xương sống của thuật toán sân shunting. Hai danh sách được sử dụng (dưới dạng ngăn xếp): O(ngăn xếp hoạt động) và B(bộ đệm đầu ra). Sau khi tất cả các mã thông báo đã được chạy, các toán tử còn lại trên Ongăn xếp được nối với bộ đệm đầu ra và kết quả được in.


2

Prolog (SWI-Prolog) , 113 byte

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).

Hãy thử trực tuyến!

SWI Prolog có một tập hợp các nội dung tốt hơn nhiều so với GNU Prolog, nhưng nó vẫn bị giữ lại phần nào bởi tính dài dòng của cú pháp của Prolog.

Giải trình

term_to_atomsẽ chạy ngược lại, phân tích một biểu thức ký hiệu (được lưu dưới dạng nguyên tử) thành cây phân tích (tuân theo các quy tắc ưu tiên thông thường và xóa các số 0 và khoảng trắng hàng đầu). Sau đó, chúng tôi sử dụng biến vị ngữ của trình trợ giúp cđể thực hiện đệ quy cấu trúc trên cây phân tích cú pháp, chuyển đổi thành ký hiệu hậu tố theo cách sâu đầu tiên.


1

Javascript (ES6), 244 byte

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` `)

Ví dụ:
Gọi: f('0-1+(2-3)*4-5*(6-(7+8)/9+10)')
Đầu ra: 0 1 - 2 3 - 4 * + 5 6 7 8 + 9 / - 10 + * -(có dấu cách)

Giải trình:

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 byte

R có thể tự phân tích cú pháp, thay vì phát minh lại bánh xe, chúng ta chỉ cần đặt trình phân tích cú pháp hoạt động, tạo ra ký hiệu tiền tố và sử dụng hàm đệ quy để chuyển nó sang ký hiệu hậu tố.

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,"")
}

Đối psố là để kiểm soát việc sử dụng đánh giá không chuẩn (nguyên nhân của các lập trình viên R ở khắp mọi nơi) và có thêm một vài ifs trong đó để kiểm soát đầu ra của dấu ngoặc (mà chúng tôi muốn tránh).

Đầu vào: (0-1+(2-3)*4-5*(6-(7+8)/9+10))

Đầu ra: 0 1 - 2 3 - 4 * + 5 6 7 8 + 9 / - 10 + * -

Đầu vào: (((((1))) + (2)))

Đầu ra: 1 2 +

Là một phần thưởng, nó hoạt động với các ký hiệu tùy ý và bất kỳ hàm được xác định trước nào có tối đa hai đối số:

Bản sắc của Euler

Đầu vào: e^(i*pi)-1

Đầu ra: e i pi * ^ 1 -

Cổ tức của 13 từ 1 đến 100

Đầu vào: which(1:100 %% 13 == 0)

Đầu ra: 1 100 : 13 %% 0 == which

Hồi quy tuyến tính trọng lượng gà con như một hàm của thời gian

Đầu vào: summary(lm(weight~Time, data=ChickWeight))

Đầu ra: weight Time ~ ChickWeight lm summary

Ví dụ cuối cùng có lẽ nằm ngoài phạm vi của OP, nhưng nó sử dụng ký hiệu hậu tố, vì vậy ...

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.