Преобразуйте инфиксные выражения в постфиксную нотацию


23

Когда я увидел название этого закрытого вопроса , я подумал, что это похоже на интересную задачу по коду в гольф. Итак, позвольте мне представить это так:

Вызов:

Написать программу, выражение или подпрограмму , которая, учитывая арифметическое выражение в инфиксной записи , как 1 + 2, выводит то же выражение в постфикса обозначений , то есть 1 2 +.

(Примечание: аналогичная проблема была опубликована ранее в январе. Однако я чувствую, что эти две задачи достаточно подробно отличаются друг от друга, чтобы оправдать эту отдельную проблему. Кроме того, я заметил другую ветку только после того, как напечатал все ниже, и я бы предпочел не просто выбросить все это.)

Входные данные:

Ввод состоит из действительного инфиксного арифметического выражения , состоящее из чисел (неотрицательные целые числа представлены в виде последовательностей одного или более десятичных цифр), сбалансированные скобки , чтобы указать , сгруппированный подвыражению, и четыре инфиксных бинарных операторов + , -, *и /. Любой из них может быть отделен (и все выражение окружено) произвольным количеством пробелов, которые следует игнорировать. 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 Единственный случай, когда наличие пробелов может повлиять на синтаксический анализ, - это когда они разделяют два последовательных числа; однако, поскольку два числа, не разделенные оператором, не могут присутствовать в действительном выражении инфикса, этот случай никогда не может произойти в допустимом вводе.

Выход:

Вывод должен быть выражением постфикса, эквивалентным вводу. Выражение выход должен состоять только из цифр и операторов, с одним пробелом между каждой парой смежных маркеров, как показано в следующей грамматике (который делает включать пробелы) 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 * +

(Это те же правила приоритета, что и в языке Си, и в большинстве языков, производных от него. Они, вероятно, напоминают правила, которым вас учили в начальной школе, за исключением, возможно, относительного приоритета *и /.)

Разные правила:

  • Если данное решение является выражением или подпрограммой, входные данные должны быть предоставлены, а выходные данные возвращены в виде одной строки. Если решение представляет собой законченную программу, оно должно прочитать строку, содержащую инфиксное выражение из стандартного ввода, и распечатать строку, содержащую постфиксную версию, в стандартный вывод.

  • Числа на входе могут включать начальные нули. Числа в выходных данных не должны иметь начальных нулей (за исключением числа 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».
Хаулет

3
@ Хаулет: Не в этом вызове, нет. Кроме того, без скобок, как бы вы проанализировали 1 2 3 4 + *?
Ильмари Каронен

Значит, запрещенные пробелы (включая символ новой строки) не разрешены?
хлебница

@breadbox: завершающие переводы строки в порядке. На самом деле, позвольте мне четко уточнить, что любые конечные пробелы разрешены.
Илмари Каронен

У меня есть решение, которое выводит «0 1 - 2 3 - 4 * 5 6 7 8 + 9 / - 10 + * - +» для последнего действительного примера, который мне кажется правильным. Вы можете проверить? (Обратите внимание на последний оператор +)
coredump

Ответы:


8

Shell утилит - 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;
Угорен

Декларация стиля K & R:*f(p,s)char*p,s;{
Угорен

1. Ошибка, если ifтест не пройден. 2. Я знаю, но функция K & R decls - это то, где я рисую линию. Я просто не могу вернуться к ним.
хлебница

Я думал, что возвращение в конце функции в любом случае. Пропустил }}и for. Но вот улучшение:printf(" %ld"+!a,...
Угорен

1
Также я думаю, что вы должны сделать pглобальный (рекурсивный вызов просто назначает вызываемого pобратно вызывающему). Тогда делай f(p=gets(b)).
Угорен

2

Баш с Хаскеллом Препроцессор C сед, 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

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)

Попробуйте онлайн!


Объяснение:

Первое, что нам нужно сделать, это преобразовать входные данные в токены. Мы делаем это с помощью поиска всех совпадений регулярного выражения \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 Prolog для этого есть гораздо лучший набор встроенных функций, чем у GNU Prolog, но он все еще несколько сдерживается многословием синтаксиса Prolog.

объяснение

term_to_atomпри обратном выполнении будет анализировать выражение инфиксной нотации (сохраняемое как атом) в дереве разбора (подчиняясь обычным правилам приоритета и удаляя начальные нули и пробелы). Затем мы используем предикат helper, 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` `)

Пример:
Call: f('0-1+(2-3)*4-5*(6-(7+8)/9+10)')
Output: 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 программистов во всем мире), и есть несколько дополнительных ifs в там , чтобы контролировать вывод данных кронштейнов (которые мы хотим избежать).

Входные данные: (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, но он использует постфиксную нотацию, так что ...

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.