Tech Blog/C and C++
C언어 - 중위 표기법을 후위 표기법으로 변환 및 계산
EXPRESSIONS HAVE POWER
2021. 6. 14. 19:13
1. 원본
// 중위표기법을 후위 표기법으로 변환
// 두 자리수 이상, 소수점 이하 까지 가능
// 음수 , sin , cos , tan 가능
#define _CRT_SECURE_NO_WARNINGS
#define PI 3.14159265358979323846
#define MAX 100
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<ctype.h>
#include<math.h>
// enum 안의 배열 값을 precedence로 치환 (struct)
typedef enum { lparen, rparen, plus, minus, times, divide, mod, eos, Sin, Cos, Tan, space, operand } precedence;
// isp = instack precedence, icp = incoming precedence
int isp[] = { 0, 19, 12, 12, 13, 13, 13, 0, 0, 0, 0 }; // 스택에 있는 값의 우선순위
int icp[] = { 20, 19, 12, 12, 13, 13, 13, 0, 20, 20, 20 }; // 스택에 들어가는 값의 우선순위
double stack[MAX]; // 스택을 정의
int top; // top포인터 정의
// 함수원형
void change_postfix(char*, char*); // 후위표현 변환 함수
precedence get_token(char*); // precedence의 형태의 값을 리턴하는 함수
char print_token(precedence); // precedence의값을 입력 받아서 문자를 리턴하는 함수
void init_stack(void); // stack을 초기화 하는 함수
void push(double); // push 하는 함수
double pop(void); // pop하는 함수
double eval(char* post); // 입력 값을 계산하는 함수
int sin_cos_tan(char* ptr); // sin, cos, tan 문자 판별 함수
// Main 함수
void main()
{
char infix[MAX] = "-10*-cos(--sin(--45--45)/2-sin(30))";
char postfix[MAX] = { 0 }; //후위표기법을 저장하는 배열
double result;
int i;
printf("수식을 입력하여주세요 : ");
gets(infix); // 중위 표기법으로 입력
change_postfix(infix, postfix); // 중위 표기법을 후위 표기법으로 바꾸는 함수
// 출력
printf("중위표기법 : %s\n", infix);
for (i = 0; postfix[i] != '\0'; i++)
{
if (postfix[i] == '$' || postfix[i] == '@' || postfix[i] == '#')
printf(")");
else
printf("%c", postfix[i]);
}
printf("\n");
result = eval(postfix); // result = 후위표기법으로 계산한 값
printf("%s = %f\n", infix, result); // 좌변은 수식, 우변은 결과값
}
// 후위 표기법으로 변환된 수식을 이용하여 값을 계산하는 함수//////
double eval(char* post)
{
char t[MAX];
precedence token;
double temp;
int i = 0, j = 0, cnt_operand = 0, cnt_operator = 0;
init_stack(); // 스택 초기화
while ((token = get_token(post)) != eos)
{
if (token == space && i > 0) // 공백을 만나면 token을 실수형으로 변환
{
t[i] = '\0';
temp = atof(t);
push(temp);
cnt_operand++;
i = 0;
}
// 문자가 아니고 operand이거나 '-'이면서 다음에 operand이면 실행
else if ((!isalpha(*post) && operand == token) || (token == minus && get_token(post + 1) == operand))
t[i++] = *post;
else // operator이면 switch문 실행
{
if (!(token >= Sin && token <= space) && !isalpha(*post) && token != lparen)
cnt_operator++;
switch (token)
{
case Sin: push(sin(pop() * PI / 180));
break;
case Cos: push(cos(pop() * PI / 180));
break;
case Tan: push(tan(pop() * PI / 180));
break;
case plus: push(pop() + pop());
break;
case minus: temp = pop();
push(pop() - temp);
break;
case times: push(pop() * pop());
break;
case divide: if ((temp = pop()) != 0);
push(pop() / temp);
break;
case mod: temp = pop();
push((int)pop() % (int)temp);
break;
}
}
post++;
}
if (cnt_operand - cnt_operator != 1)
{
puts("에러!!!!!!!!!!. 수식이 맞지 않습니다.");
exit(1);
}
return pop();
}
// 중위표기법을 후위표기법으로 변환 시키는 함수
void change_postfix(char* str, char* postfix)
{
int i = 0, j = 0, flag = 0, count = 0, s_c_t;
precedence token;
init_stack(); // 스택 초기화
push(eos); // 우선순위가 가장 낮은 '\0'를 삽입
while ((token = get_token(str + i)) != eos) // str이 널값이면 변환 중지
{
if (token == operand) // operand이면 postfix에 바로 삽입
{
if (s_c_t = sin_cos_tan(str + i))
{
if (count % 2) // -1 = (0-1) 이기때문에 ==> 0 1 - 로 표현
{
strncat(postfix + j, "0 ", 2); // operand 뒤에 공백 삽입
j += 2;
push(minus);
// push(token);
count = 0;
}
strncat(postfix + j, s_c_t == Sin ? "sin" : s_c_t == Cos ? "cos" : "tan", 3);
push(s_c_t);
if (lparen == get_token(str + i + 3))
{
strncat(postfix + j, "( ", 2);
i++;
j += 2;
}
j += 3;
i += 3;
continue;
}
else if (count && count % 2)
{
*(postfix + j++) = '-';
count = 0;
}
*(postfix + j++) = *(str + i);
flag = 1; // operand가 실행 되었다는것을 표시
}
else if (token == lparen && count % 2)
{
strncat(postfix + j, "0 ", 2); // operand 뒤에 공백 삽입
j += 2;
push(minus);
push(token);
count = 0;
}
// 음수 판별 조건 : token은 '-' 이고, operand 또는(or) ')' 바로 뒤에 '-'는 뺄셈 부호임
// 따라서 operand가 선행 안되면서(and), ')'가 이전에 없으면 '-'는 부호 표시 기호이다.
else if (token == minus && !flag && get_token(str + i - 1) != rparen)
count++;
else
{
if (flag) // operand와 구분 시켜 주기위해 공백 삽입, flag가 1이면 operand가 삽입 되었단 말임
{
*(postfix + j++) = ' ';
flag = 0;
}
if (token == rparen) // token이 ')'이면 '('이 나올때까지 postfix에 삽입
{
while ((token = (int)pop()) != lparen && !(token >= Sin && token <= Tan))
{
*(postfix + j++) = ' ';
*(postfix + j++) = print_token(token);
*(postfix + j++) = ' ';
}
if (token != lparen) // sin => $ , cos => @ , tan => # 기호 삽입
{
*(postfix + j++) = print_token(token);
*(postfix + j++) = ' ';
}
}
else
{ // token값이 stack[top]값보다 우선순위가 높을때까지 postfix에 삽입
while (icp[token] <= isp[(int)stack[top]])
{
*(postfix + j++) = ' ';
*(postfix + j++) = print_token((int)pop());
*(postfix + j++) = ' ';
}
push(token); // 마지막에 token값을 푸쉬
}
}
i++;
}
*(postfix + j++) = ' '; // operand와 구분 시켜 주기위해 공백 삽입
while ((token = (int)pop()) != eos) // 스택에 남은 값들을 postfix에 삽입
*(postfix + j++) = print_token(token);
*(postfix + j) = '\0'; // postfix 맨마지막에 null 삽입
}
// sin, cos, tan 문자 판별 함수
precedence sin_cos_tan(char* ptr)
{
if (!strncmp(ptr, "sin", 3))
return Sin;
if (!strncmp(ptr, "cos", 3))
return Cos;
if (!strncmp(ptr, "tan", 3))
return Tan;
return 0;
}
// precedence의 형태의 값을 리턴하는 함수
precedence get_token(char* symbol)
{
switch (*symbol)
{
case '(': return lparen;
case ')': return rparen;
case '+': return plus;
case '-': return minus;
case '*': return times;
case '/': return divide;
case '%': return mod;
case '\0': return eos;
case ' ': return space;
case '$': return Sin;
case '@': return Cos;
case '#': return Tan;
default: return operand;
}
}
// precedence의값을 입력 받아서 문자를 리턴하는 함수
char print_token(precedence token)
{
switch (token)
{
case plus: return '+';
case minus: return '-';
case times: return '*';
case divide: return '/';
case mod: return '%';
case Sin: return '$';
case Cos: return '@';
case Tan: return '#';
default: return '\0';
}
}
// 스택 초기화 함수
void init_stack(void)
{
top = -1;
}
//스택에 푸쉬 하는 함수
void push(double in)
{
if (top >= MAX - 1)
{
puts("Stack is Full !!!!!!!!!!!");
exit(1);
}
stack[++top] = in;
}
// 스택에서 팝하는 함수
double pop(void)
{
if (top < 0)
{
puts("Stack is Empty !!!!!!!!!!!");
exit(1);
}
return stack[top--];
}
결과 값
2. 변형 (sin,cos,tan 빼고 pi등 뺀 것.) (두 자리 이상과 마이너스만 살림)
#define _CRT_SECURE_NO_WARNINGS
#define MAX 100
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <math.h>
typedef enum { lparen, rparen, plus, minus, times, divide, mod, eos, space, operand } precedence;
static int icp[] = { 20, 19, 12, 12, 13, 13, 13, 0 };
static int isp[] = { 0, 19, 12, 12, 13, 13, 13, 0 };
double stack[MAX];
int top;
// 함수 원형
void change_postfix(char*, char*);
precedence get_token(char*);
char print_token(precedence);
void init_stack(void);
void push(double);
double pop(void);
double eval(char* post);
void main()
{
char infix[MAX] = { 0 };
char postfix[MAX] = { 0 };
double result;
int i;
printf("수식을 입력하여 주세요 : ");
gets(infix);
change_postfix(infix, postfix);
printf("중위표기법 : %s\n", infix);
for (i = 0; postfix[i] != '\0'; i++)
{
printf("%c", postfix[i]);
}
printf("\n");
result = eval(postfix);
printf("%s = %f\n", infix, result);
}
double eval(char* post)
{
char t[MAX];
precedence token;
double temp;
int i = 0, j = 0, cnt_operand = 0, cnt_operator = 0;
init_stack(); // 스택 초기화
while ((token = get_token(post)) != eos)
{
if (token == space && i > 0) // 공백을 만나면 token을 실수형으로 변환
{
t[i] = '\0';
temp = atof(t);
push(temp);
cnt_operand++;
i = 0;
}
// 문자가 아니고 operand이거나 '-'이면서 다음에 operand이면 실행
else if ((!isalpha(*post) && operand == token) || (token == minus && get_token(post + 1) == operand))
t[i++] = *post;
else // operator이면 switch문 실행
{
if (token <= space && !isalpha(*post) && token != lparen)
{
cnt_operator++;
switch (token)
{
case plus: push(pop() + pop());
break;
case minus: temp = pop();
push(pop() - temp);
break;
case times: push(pop() * pop());
break;
case divide: if ((temp = pop()) != 0);
push(pop() / temp);
break;
case mod: temp = pop();
push((int)pop() % (int)temp);
break;
}
}
}
post++;
}
return pop();
}
void change_postfix(char* str, char* postfix)
{
int i = 0, j = 0, flag = 0, count = 0;
precedence token;
init_stack();
push(eos);
while ((token = get_token(str + i)) != eos)
{
if (token == operand)
{
if (count && count % 2)
{
*(postfix + j++) = '-';
count = 0;
}
*(postfix + j++) = *(str + i);
flag = 1;
}
else if (token == lparen && count % 2)
{
strncat(postfix + j, "0", 2);
j += 2;
push(minus);
push(token);
count = 0;
}
else if (token == minus && !flag && get_token(str + i - 1) != rparen)
count++;
else
{
if (flag)
{
*(postfix + j++) = ' ';
flag = 0;
}
if (token == rparen)
{
while ((token = (int)pop()) != lparen)
{
*(postfix + j++) = ' ';
*(postfix + j++) = print_token(token);
*(postfix + j++) = ' ';
}
if (token != lparen)
{
*(postfix + j++) = print_token(token);
*(postfix + j++) = ' ';
}
}
else
{
while (icp[token] <= isp[(int)stack[top]])
{
*(postfix + j++) = ' ';
*(postfix + j++) = print_token((int)pop());
*(postfix + j++) = ' ';
}
push(token);
}
}
i++;
}
*(postfix + j++) = ' ';
while ((token = (int)pop()) != eos)
*(postfix + j++) = print_token(token);
*(postfix + j) = '\0';
}
precedence get_token(char* symbol)
{
switch (*symbol)
{
case '(': return lparen;
case ')': return rparen;
case '+': return plus;
case '-': return minus;
case '*': return times;
case '/': return divide;
case '%': return mod;
case '\0': return eos;
case ' ': return space;
default: return operand;
}
}
char print_token(precedence token)
{
switch (token)
{
case plus: return '+';
case minus: return '-';
case times: return '*';
case divide: return '/';
case mod: return '%';
default: return '\0';
}
}
void init_stack(void)
{
top = -1;
}
void push(double in)
{
if (top >= MAX - 1)
{
puts("Stack is Full!!!");
exit(1);
}
stack[++top] = in;
}
double pop(void)
{
if (top < 0)
{
puts("Stack is Empty!!!!");
exit(1);
}
return stack[top--];
}
-10*-(-10*(-10--20)) = -1000
다른거는 되는데 , 위에거 안됨
*- 붙어있는 걸 처리 못하는 듯.
이것처럼 되어야 하는데..