접두사 표현식을 접미사 표기법으로 변환


23

이 닫힌 질문 의 제목을 보았을 때 , 흥미로운 코드 골프 도전처럼 보였습니다. 따라서 다음과 같이 발표하겠습니다.

도전:

산술 식의 지정된, 프로그램, 서브 루틴 또는 식 작성 중위 표기 , 등 1 + 2, 동일한 표현 출력 후위 표기 , 즉 1 2 +.

(참고 : 비슷한 도전이 1 월 초에 게시 되었지만,이 두 가지 과제가이 별도의 도전을 정당화하기에 충분히 세부적으로 다르다고 생각합니다. 또한 아래의 모든 내용을 입력 한 후에 다른 스레드 만 발견했습니다. 그냥 버리지 말고)

입력:

입력이 이루어지는 유효한 중위 산술 식 구성 번호 (음이 아닌 정수 하나 개 이상의 십진수의 시퀀스로 표현), 균형 괄호 그룹화 표현식을 나타내고, 네 중위 이진 연산자 + , -, */. 이들 중 임의의 수의 공백 문자로 이들을 분리 (및 전체 표현식으로 묶을 수 있음) 할 수 있습니다.이 문자는 무시해야합니다. 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"

하나 가 두 개의 연속 된 숫자를 분리 할 때 공간의 존재는 분석에 영향을 줄 수있는 경우에만이고; 그러나 연산자로 분리되지 않은 두 개의 숫자는 유효한 접 두부 표현식에서 발생할 수 없으므로이 경우에는 유효한 입력에서 발생할 수 없습니다.

산출:

출력은 입력과 동등한 접미사 표현식이어야합니다. 출력 식으로, 숫자와 연산자 구성되어야 단일 공백 문자 다음 문법 (여기서 같이 인접 토큰들의 각 쌍 사이 않은 공간을 포함하는) 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를 위해이 문법 의 생성은 아래 규칙에 따라 출력이 금지 되더라도 앞에 0이있는 숫자를 허용합니다.

연산자 우선 순위 :

괄호가 없으면 다음과 같은 우선 순위 규칙이 적용됩니다.

  • 연산자 */보다 높은 우선 순위를 가지고 +-.
  • 연산자 */서로 우선 순위가 같습니다.
  • 연산자 +-서로 우선 순위가 같습니다.
  • 모든 연산자는 왼쪽 연관입니다.

예를 들어 다음 두 표현식은 동일합니다.

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

그리고 둘 다 다음과 같은 결과를 산출해야합니다.

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

(이 같은 우선 순위 규칙은 C 언어로 그것에서 파생 된 대부분의 언어에 있습니다. 그들은 아마, 당신이 초등학교에서 배운 규칙을 닮은의 상대적 우선 순위에 대한 가능성 제외시켰다 */.)

기타 규칙 :

  • 제공된 솔루션이 표현식 또는 서브 루틴 인 경우 입력을 제공하고 출력을 단일 문자열로 리턴해야합니다. 솔루션이 완전한 프로그램 인 경우 표준 입력에서 접두사 표현식이 포함 된 행을 읽고 접미사 버전이 포함 된 행을 표준 출력으로 인쇄해야합니다.

  • 입력의 숫자 는 선행 0을 포함 할 수 있습니다. 출력에서의 숫자는 안된다 (로 출력한다 숫자 0을 제외하고 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)"

Lisp과 같은 표기법이 허용됩니까? 예를 들어 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 제안은 작동하지 않지만 연속 연산자는 더 이상 공백이 없기 때문에 죄송합니다. 나도 그 문제를 해결해야합니다
Geoff 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글로벌로 만들어야한다고 생각합니다 (재귀 호출은 수신자를 p다시 발신자에게 할당합니다 ). 그런 다음 f(p=gets(b)).
ugoren

2

Haskell 승 / 배쉬 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
배스? ( 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하여 토큰 유형을 기반으로 해당 스 니펫을 실행 한 후 큰 튜플에 색인을 생성 합니다. 이것은 각 토큰마다 다르며 분로 장 알고리즘의 중추입니다. 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 프롤로그는 GNU 프롤로그보다이 기능을 훨씬 더 잘 갖추고 있지만 여전히 프롤로그 구문의 상세한 설명에 의해 다소 보류됩니다.

설명

term_to_atom거꾸로 실행되는 경우, 원자로 저장된 접두어 표기법 표현식을 구문 분석 트리로 구문 분석합니다 (일반적인 우선 순위 규칙을 따르고 선행 0 및 공백 삭제). 그런 다음 도우미 술어 c를 사용하여 구문 분석 트리에서 구조적 재귀를 수행하여 깊이 우선 방식으로 접미사 표기법으로 변환합니다.


1

자바 스크립트 (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(우리가 피하려고) 브래킷의 출력을 제어하는가에들.

입력: (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 -

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.