Перетворити вирази інфіксації в позначення постфікса


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

3
@Hauleth: Не в цьому виклику, ні. До того ж, без дужок, як би ви розібралися 1 2 3 4 + *?
Ільмарі Каронен

Отже, жодний пробіл пробілу (включаючи новий рядок) не допускається в otuput?
хлібниця

@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 символьну вартість.
угорен

oops, хоча пропозиція @ugoren не працює, оскільки послідовні оператори більше не мають місця між ними; Я теж повинен виправити це
Джефф Реді

4

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

Приймає введення як параметри командного рядка. Файл wіз деякими попередженнями ghc буде створено, якщо вам не сподобається ця зміна runghc 2>/dev/null.


1
Погріли? ( Bas h + H aske ll + s ed )
КалькуляторFeline

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

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буде, якщо запустити назад, проаналізувати вираз інфіксації (зберігається як атом) на дерево розбору (дотримуючись звичайних правил пріоритету та видаливши провідні нулі та пробіли). Тоді ми використовуємо предикатор 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` `)

Приклад:
Виклик: 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 +

Як бонус, він працює з довільними символами та будь-якими попередньо визначеними функціями, що мають до двох аргументів:

Особистість Ейлера

Вхід: 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

Останній приклад, можливо, трохи виходить за рамки ОП, але він використовує позначення постфікса, тому ...

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.