แปลงการแสดงออกมัดเป็นสัญลักษณ์ postfix


23

เมื่อฉันเห็นชื่อของคำถามที่ปิดนี้ฉันคิดว่ามันดูเหมือนการแข่งขันกอล์ฟที่น่าสนใจ ดังนั้นฉันขอเสนอเป็นเช่นนี้:

ท้าทาย:

เขียนโปรแกรมการแสดงออกหรือย่อยซึ่งได้รับการแสดงออกเกี่ยวกับคณิตศาสตร์ในสัญกรณ์มัดเช่น1 + 2ผลลัพธ์ที่ได้แสดงออกเหมือนกันในสัญกรณ์ postfix1 2 +คือ

(หมายเหตุ: ความท้าทายที่คล้ายกันถูกโพสต์เมื่อต้นเดือนมกราคม อย่างไรก็ตามฉันรู้สึกว่างานทั้งสองนั้นมีความแตกต่างกันพอสมควรเพื่อแสดงให้เห็นถึงความท้าทายที่แยกต่างหากนี้นอกจากนี้ฉันสังเกตเห็นหัวข้ออื่นหลังจากพิมพ์ทุกอย่างด้านล่าง ไม่เพียงทิ้งมันไป)

การป้อนข้อมูล:

การป้อนข้อมูลประกอบด้วยมัดที่ถูกต้องแสดงออกคณิตศาสตร์ซึ่งประกอบด้วยตัวเลข (จำนวนเต็มไม่เป็นลบแสดงเป็นลำดับหนึ่งหรือตัวเลขทศนิยมเพิ่มเติม) สมดุลวงเล็บเพื่อระบุ subexpression จัดกลุ่มและสี่มัดไบนารีผู้ประกอบการ + , -, และ* /สิ่งเหล่านี้อาจถูกแยกออก (และนิพจน์ทั้งหมดล้อมรอบ) ด้วยจำนวนช่องว่างโดยพลการซึ่งควรละเว้น 1

สำหรับผู้ที่ชื่นชอบไวยากรณ์อย่างเป็นทางการนี่เป็นไวยากรณ์ที่เหมือน BNF ง่าย ๆ ที่กำหนดอินพุตที่ถูกต้อง เพื่อความกระชับและชัดเจนไวยากรณ์จะไม่รวมช่องว่างเพิ่มเติมซึ่งอาจเกิดขึ้นระหว่างโทเค็นใด ๆ สอง (นอกเหนือจากตัวเลขภายในตัวเลข):

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กรณีเดียวที่มีช่องว่างอาจส่งผลกระทบต่อการแยกคือเมื่อพวกเขาแยกสองหมายเลขติดต่อกัน; อย่างไรก็ตามเนื่องจากตัวเลขสองตัวที่ไม่ได้คั่นด้วยตัวดำเนินการไม่สามารถเกิดขึ้นในนิพจน์มัดที่ถูกต้องกรณีนี้จะไม่เกิดขึ้นในอินพุตที่ถูกต้อง

เอาท์พุท:

เอาต์พุตควรเป็นนิพจน์ postfix ที่เทียบเท่ากับอินพุต การแสดงออกการส่งออกควรจะมีเพียงของตัวเลขและผู้ประกอบการที่มีอักขระช่องว่างเดียวระหว่างคู่ของสัญญาณที่อยู่ติดกันแต่ละเช่นเดียวกับในไวยากรณ์ต่อไปนี้ (ซึ่งไม่รวมถึงช่องว่าง) 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ผลิตในไวยากรณ์นี้ยอมรับตัวเลขด้วยเลขศูนย์นำหน้าแม้ว่าจะถูกห้ามในผลลัพธ์ตามกฎด้านล่าง

ผู้ประกอบการที่มีความสำคัญ:

ในกรณีที่ไม่มีวงเล็บจะใช้กฎที่สำคัญกว่าดังต่อไปนี้:

  • ผู้ประกอบการ*และ/มีความสำคัญสูงกว่าและ+-
  • ผู้ประกอบการ*และ/มีความสำคัญเท่าเทียมกัน
  • ผู้ประกอบการ+และ-มีความสำคัญเท่าเทียมกัน
  • ผู้ประกอบการทั้งหมดมีความสัมพันธ์ด้านซ้าย

ตัวอย่างเช่นสองนิพจน์ต่อไปนี้เทียบเท่า:

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

และควรให้ผลลัพธ์ต่อไปนี้:

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

(เหล่านี้เป็นกฎความสำคัญเช่นเดียวกับในภาษา C และในภาษาส่วนใหญ่ได้มาจากมัน. พวกเขาอาจจะมีลักษณะคล้ายกับกฎที่คุณได้รับการสอนในโรงเรียนประถมยกเว้นอาจจะเป็นเพราะความสำคัญของญาติ*และ/.)

กฎเบ็ดเตล็ด:

  • หากวิธีการแก้ปัญหาที่ได้รับคือการแสดงออกหรือรูทีนย่อยควรป้อนข้อมูลและการส่งออกกลับเป็นสตริงเดียว หากการแก้ปัญหาเป็นโปรแกรมที่สมบูรณ์ก็ควรอ่านบรรทัดที่มีการแสดงออกของ infix จากอินพุตมาตรฐานและพิมพ์บรรทัดที่มีรุ่น postfix ไปยังเอาต์พุตมาตรฐาน

  • ตัวเลขในอินพุตอาจรวมศูนย์นำหน้าด้วย ตัวเลขในเอาต์พุตต้องไม่มีศูนย์นำหน้า (ยกเว้นสำหรับหมายเลข 0 ซึ่งจะเป็นเอาต์พุต0)

  • คุณไม่คาดว่าจะประเมินหรือปรับการแสดงออกในทางใดทางหนึ่ง โดยเฉพาะอย่างยิ่งคุณไม่ควรสันนิษฐานว่าผู้ปฏิบัติงานจำเป็นต้องมีความเกี่ยวข้องกับการเชื่อมโยงการสับเปลี่ยนหรืออัตลักษณ์เชิงพีชคณิตอื่น ๆ นั่นคือคุณไม่ควรคิดว่าเช่น1 + 2เท่ากับ2 + 1หรือเท่ากับ1 + (2 + 3)(1 + 2) + 3

  • คุณอาจคิดว่าตัวเลขในอินพุตไม่เกิน 2 31 - 1 = 2147483647

กฎเหล่านี้มีจุดประสงค์เพื่อให้แน่ใจว่าผลลัพธ์ที่ถูกต้องนั้นถูกกำหนดไว้อย่างไม่ซ้ำกันโดยอินพุต

ตัวอย่าง:

ต่อไปนี้เป็นนิพจน์อินพุตที่ถูกต้องและเอาต์พุตที่สอดคล้องกันซึ่งแสดงในรูปแบบ"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)"

เสียงกระเพื่อมเป็นสัญกรณ์ยอมรับได้หรือไม่ ตัวอย่างเช่น1 2 3 4 +หมายถึง `1 + 2 + 3 + 4`
Hauleth

3
@Hauleth: ไม่ได้อยู่ในความท้าทายนี้ไม่ นอกจากนี้หากไม่มีวงเล็บคุณจะแยกวิเคราะห์1 2 3 4 + *อย่างไร
Ilmari Karonen

ดังนั้นไม่อนุญาตให้มีช่องว่างต่อท้าย (รวมถึงการขึ้นบรรทัดใหม่) ใน otuput?
breadbox

@breadbox: ขึ้นบรรทัดใหม่ตามปกติ ที่จริงแล้วฉันขอชี้แจงอย่างชัดเจนว่าอนุญาตให้ใช้ช่องว่างที่ต่อท้ายได้
Ilmari Karonen

ฉันมีทางออกที่ออก "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

@breadbox sed -re's/[:@iKWr]+/ /g'แก้ไขได้ที่ 1 ตัวอักษร
ugoren

อ๊ะถึงแม้ว่าคำแนะนำ @ugoren จะไม่ทำงานเนื่องจากผู้ให้บริการที่ต่อเนื่องกันไม่มีช่องว่างระหว่างพวกเขาอีกต่อไป ฉันต้องมาแก้ไขด้วยเช่นกัน
เจฟฟ์ Reedy

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 ปฏิเสธว่าฉันอยู่ตรงไหน ฉันไม่สามารถกลับไปหาพวกเขาได้
breadbox

ฉันคิดว่าการคืนสินค้าจะสิ้นสุดที่ฟังก์ชันต่อไป พลาดและ}} forแต่นี่คือการลบล้าง:printf(" %ld"+!a,...
ugoren

1
นอกจากนี้ฉันคิดว่าคุณควรจะทำให้pทั่วโลก (เรียกซ้ำเพียงแค่กำหนด callee pกลับไปที่ผู้โทร) f(p=gets(b))แล้วทำ
ugoren

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

รับอินพุตเป็นพารามิเตอร์บรรทัดคำสั่ง ไฟล์ที่wมีข้อความเตือน ghc บางอย่างจะถูกสร้างขึ้นหากคุณไม่ชอบการเปลี่ยนแปลงrunghc 2>/dev/nullนี้


1
basked? ( Bas h + H aske ll + s ed )
CalculatorFeline

2

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

ลองออนไลน์!


คำอธิบาย:

สิ่งแรกที่เราต้องทำคือแปลงอินพุตเป็นโทเค็น เราใช้วิธีนี้โดยค้นหาการจับคู่ทั้งหมดของ regex \d+|\Sแปลโดยประมาณเป็น "กลุ่มตัวเลขใด ๆ และอักขระที่ไม่ใช่ช่องว่าง" สิ่งนี้จะลบช่องว่างแยกวิเคราะห์ตัวเลขที่อยู่ติดกันเป็นโทเค็นเดียวและแยกวิเคราะห์โอเปอเรเตอร์แยกกัน

สำหรับอัลกอริทึมการหลบหลีกมี 4 ประเภทโทเค็นที่แตกต่างที่เราต้องจัดการ:

  • ( - วงเล็บซ้าย
  • ) - วงเล็บขวา
  • +-*/ - ผู้ประกอบการ
  • 9876543210 - ตัวอักษรตัวเลข

โชคดีที่รหัส ASCII ของรหัสเหล่านี้ถูกจัดกลุ่มตามลำดับที่แสดงดังนั้นเราจึงสามารถใช้นิพจน์(t>'/')+(t>')')+(t>'(')เพื่อคำนวณประเภทโทเค็น ผลลัพธ์นี้เป็น3สำหรับตัวเลข2สำหรับโอเปอเรเตอร์1สำหรับวงเล็บขวาและ0สำหรับวงเล็บซ้าย

การใช้ค่าเหล่านี้เราจะจัดทำดัชนีเป็น tuple ขนาดใหญ่หลังจากexecได้รับตัวอย่างข้อมูลที่เกี่ยวข้องเพื่อดำเนินการตามชนิดของโทเค็น สิ่งนี้แตกต่างกันไปสำหรับแต่ละโทเค็นและเป็นกระดูกสันหลังของอัลกอริทึมการหลบหลีก มีการใช้สองรายการ (เป็นสแต็ก): O(สแต็กการดำเนินการ) และB(บัฟเฟอร์เอาต์พุต) หลังจากโทเค็นทั้งหมดถูกเรียกใช้แล้วตัวดำเนินการที่เหลืออยู่บนOสแต็กจะต่อกันกับบัฟเฟอร์เอาต์พุตและผลลัพธ์จะถูกพิมพ์


2

Prolog (SWI-Prolog) , 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จะถ้าวิ่งไปข้างหลังให้แจง infix-notation expression (เก็บเป็นอะตอม) ลงใน parse tree (ปฏิบัติตามกฎที่มีมาก่อนตามปกติและลบเลขศูนย์นำและช่องว่าง) จากนั้นเราจะใช้ผู้ช่วยกริยา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 สามารถแยกวิเคราะห์ตัวเองได้ดังนั้นแทนที่จะสร้างวงล้อขึ้นใหม่เราเพียงวาง parser ให้ทำงานซึ่งแสดงสัญลักษณ์คำนำหน้าและใช้ฟังก์ชั่นวนซ้ำเพื่อสลับเป็นสัญกรณ์ postfix

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วินาทีในการมีการควบคุมการ outputting ของวงเล็บ (ซึ่งเราต้องการที่จะหลีกเลี่ยง)

การป้อนข้อมูล: (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 +

เป็นโบนัสมันใช้งานได้กับสัญลักษณ์โดยพลการและฟังก์ชั่นใด ๆ ที่กำหนดไว้ล่วงหน้าที่มีอาร์กิวเมนต์สูงสุดสองข้อ:

เอกลักษณ์ของออยเลอร์

การป้อนข้อมูล: e^(i*pi)-1

เอาท์พุท: e i pi * ^ 1 -

เงินปันผลจาก 13 ระหว่าง 1 ถึง 100

การป้อนข้อมูล: which(1:100 %% 13 == 0)

เอาท์พุท: 1 100 : 13 %% 0 == which

การถดถอยเชิงเส้นของน้ำหนักไก่เนื้อเป็นฟังก์ชันของเวลา

การป้อนข้อมูล: summary(lm(weight~Time, data=ChickWeight))

เอาท์พุท: weight Time ~ ChickWeight lm summary

ตัวอย่างสุดท้ายอาจจะอยู่นอกขอบเขตของ OP เล็กน้อย แต่มันใช้สัญกรณ์ postfix ดังนั้น ...

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.