Code for working with mathematical expressions in infix and postfix notations, implementing the following main operations:
- Conversion of infix notation to postfix notation (Reverse Polish Notation)
- Evaluation of postfix expressions
- Plotting graphs of mathematical expressions within a specified range
The code is written in C and uses a stack-based approach.
- Features
- Project Structure
- Input of the Initial String
- Dijkstra's Algorithm - Conversion of Infix Notation to Postfix
- Postfix Notation Calculator
- Graph Plotting
- Usage Example
-
Supported Mathematical Operators:
+
,-
(unary and binary)*
,/
,^
-
Supported Functions:
sin
,cos
,tan
,cot
,sqrt
,ln
,exp
-
Supported Constants
PI
-
Description:
- The expression is converted using a stack for operators.
-
Supported Mathematical Operators:
+
,-
(binary),~
(unary)*
,/
,^
-
Supported Functions:
sin
,cos
,tan
,cot
,sqrt
,ln
-
Description:
- The expression is evaluated using a stack for operands.
- Functionality:
- Computes an array of values for the expression within a specified range.
- Plots a simple graph using text symbols in the terminal.
polish_notation/
├── build/ # Directory for executable files
│ └── (automatically created during build using Makefile)
├── images/ # Directory for images used in README
├── README.md # README in English
├── README_RUS.md # README in Russian
└── src/ # Source code of the project
├── dejkstra_alg/ # Implementation of Dijkstra's algorithm
├── graph/ # Module for graph plotting
├── input/ # Module for expression input
├── objs/ # Directory for object files
├── polish_calc/ # Module for calculating expressions in postfix notation
├── stack/ # Stack implementation
├── whotype/ # Helper functions (similar to ctype)
├── main.c # Entry point of the program, calls main functions
└── Makefile # Makefile for building the project
./src/input/
To input the initial string to be processed, functions described in the files input_expr.c
and input_expr.h
are used. The main input function:
input_expr(char **const str)
: Takes a pointer to a string where the entered expression will be stored. Returns the length of the string. Possible return codes:0
: The string is empty-1
: Memory allocation error
To simplify working with the expression, the multiplication sign between numbers and functions or variables can be omitted (4x
, 5sin(0.5x)
). The following functions are implemented for this purpose:
-
int space_clear(char **const str, int const len)
: Removes unnecessary spaces from the string. Example: converts2 sin(x)
to2sin(x)
, but leaves4 4
unchanged. -
int insert_mul(char **const str, int const len)
: Inserts missing multiplication signs in expressions where necessary. Example: converts4x
to4*x
.
An error output function is also provided:
input_err(int const err_code)
: Outputs the error code and its description to the standard error stream.
The project implements Dijkstra's algorithm to convert mathematical expressions from infix notation to postfix notation (Reverse Polish Notation).
In the module expr_logic_check.c
, the following function is implemented:
int check_expression(char const *const original_expression,
char const *const modified_expression);
This function takes the original expression and the modified expression as input, performing a logical correctness check. The check includes:
- Ignoring Spaces: All spaces in the expression are skipped.
- Example:
2+2 *2
— valid2+2 2
— error
- Example:
- Balance of Parentheses: The number of opening and closing parentheses must match.
- Valid Characters: Only digits, operators
+
,-
(binary and unary),*
,/
,^
,~
, and functionssin
,cos
,tan
,cot
,sqrt
,ln
are allowed. - Order Check:
- The first element cannot be a binary operator (
+2
— error) - After each operand, there must be a binary operator or the end of the expression
- After each binary operator, another binary operator must not follow
- Any number of unary minuses are allowed (
----5
— error)
- The first element cannot be a binary operator (
./src/dejkstra_alg/
In the file dejkstra.c
, Dijkstra's algorithm is implemented in the function:
char *infix_to_postfix(char const *const infix);
This function takes a string in infix form and returns a string in postfix form, where all elements (operands and operators) are separated by spaces, or NULL
in case of errors.
char *infix_to_postfix(char const *const infix) {
char const *const modified_infix = replace_unary_minus(infix);
if (!modified_infix)
return NULL;
if (!check_expression(infix, modified_infix)) {
free((void *)modified_infix);
return NULL;
}
char const *const postfix = do_postfix_convert(modified_infix);
free((void *)modified_infix);
return (char *const)postfix;
}
-
Replacement of Unary Minus:
The function
replace_unary_minus
processes the expression, replacing unary minuses with a special symbol (~
) to avoid confusion with binary minus. This is necessary, for example, when converting the expression2/(-1+2)
. -
Expression Check:
After replacing the unary minus, the function
check_expression
verifies the correctness of the expression. -
Conversion to Postfix Form:
If the expression is correct, the function
do_postfix_convert
is called for the actual conversion.char *do_postfix_convert(char const *const modified_infix) { Stack_int operator_stack; initStack_int(&operator_stack); int const length = strlen(modified_infix); char *const postfix = (char *)malloc(4 * length * sizeof(char)); if (!postfix) { return NULL; } int i = 0, j = 0; while (modified_infix[i] != '\0') { if (isSpace(modified_infix[i])) { i++; continue; } if (isDigit(modified_infix[i])) { parse_digits(modified_infix, &i, postfix, &j); } else if (isLetter(modified_infix[i])) { parse_letter(modified_infix, &i, postfix, &j, &operator_stack); } else if (isOperator(modified_infix[i]) || modified_infix[i] == '(' || modified_infix[i] == ')') { parse_operator_parentheses(modified_infix, &i, postfix, &j, &operator_stack); } } pop_until_empty(&operator_stack, postfix, &j); postfix[j] = '\0'; destroyStack_int(&operator_stack); return postfix; }
-
Token Parsing and Processing:
-
Digits:
- The function
parse_digits
handles numeric values and adds them to the postfix string.
- The function
-
Functions and Variables:
- The function
parse_letter
handles functions (sin
,cos
, etc.) and variables, adding variables to the postfix string or functions to the operator stack.
- The function
-
Operators and Parentheses:
- Opening Parenthesis
(
:- Pushed onto the operator stack.
- Closing Parenthesis
)
:- Operators are popped from the stack until
(
is encountered and added to the postfix expression.
- Operators are popped from the stack until
- Operators (
+
,-
,*
,/
,^
,~
):- Handled considering precedence and associativity.
- Opening Parenthesis
-
-
Popping Remaining Operators from the Stack:
After traversing the entire string, remaining operators are popped from the stack and added to the postfix string.
Let's consider the algorithm's operation using the expression -2*ln(x)/(4*-2*sin(5*x))
.
-
Replacement of Unary Minuses:
Original Expression:
-2*ln(x)/(4*-2*sin(5*x))
Modified Expression:
~2*ln(x)/(4*~2*sin(5*x))
-
Step-by-Step Parsing:
Step | Current Symbol | Action | Postfix String | Operator Stack |
---|---|---|---|---|
1 | ~ |
Operator ~ (unary minus): Pushed onto the operator stack. |
"" | [~] |
2 | 2 |
Operand 2 : Added to the postfix string. |
2 |
[~] |
3 | * |
Operator * : Precedence * (3) higher than ~ (2). Operator * is pushed onto the stack. |
2 |
[~, *] |
4 | l |
Function ln : Read as the entire token ln and pushed onto the operator stack. |
2 |
[~, *, ln] |
5 | ( |
Opening Parenthesis ( : Pushed onto the operator stack. |
2 |
[~, *, ln, (] |
6 | x |
Operand x : Added to the postfix string. |
2 x |
[~, *, ln, (] |
7 | ) |
Closing Parenthesis ) : Operators are popped from the stack until ( is encountered.- ln is popped and added to the postfix string.- ( is removed from the stack. |
2 x ln |
[~, *] |
8 | / |
Operator / : Precedence / (3) equals precedence * (3). Since / is left-associative:- * is popped and added to the postfix string.- / is pushed onto the stack. |
2 x ln * |
[~, /] |
9 | ( |
Opening Parenthesis ( : Pushed onto the operator stack. |
2 x ln * |
[~, /, (] |
10 | 4 |
Operand 4 : Added to the postfix string. |
2 x ln * 4 |
[~, /, (] |
11 | * |
Operator * : Precedence * (3) higher than ( . Operator * is pushed onto the stack. |
2 x ln * 4 |
[~, /, (, *] |
12 | ~ |
Operator ~ : Precedence ~ (2) lower than * (3).- * is popped and added to the postfix string.- ~ is pushed onto the stack. |
2 x ln * 4 * |
[~, /, (, ~] |
13 | 2 |
Operand 2 : Added to the postfix string. |
2 x ln * 4 * 2 |
[~, /, (, ~] |
14 | * |
Operator * : Precedence * (3) higher than ~ (2). Operator * is pushed onto the stack. |
2 x ln * 4 * 2 |
[~, /, (, ~, *] |
15 | s |
Function sin : Read as the entire token sin and pushed onto the operator stack. |
2 x ln * 4 * 2 |
[~, /, (, ~, *, sin] |
16 | ( |
Opening Parenthesis ( : Pushed onto the operator stack. |
2 x ln * 4 * 2 |
[~, /, (, ~, *, sin, (] |
17 | 5 |
Operand 5 : Added to the postfix string. |
2 x ln * 4 * 2 5 |
[~, /, (, ~, *, sin, (] |
18 | * |
Operator * : Precedence * (3) higher than ( . Operator * is pushed onto the stack. |
2 x ln * 4 * 2 5 |
[~, /, (, ~, *, sin, (, *] |
19 | x |
Operand x : Added to the postfix string. |
2 x ln * 4 * 2 5 x |
[~, /, (, ~, *, sin, (, *] |
20 | ) |
Closing Parenthesis ) : Operators are popped from the stack until ( is encountered.- * is popped and added to the postfix string.- sin is popped and added to the postfix string.- ( is removed from the stack. |
2 x ln * 4 * 2 5 x * sin |
[~, /, ~] |
21 | End of Expression | Completion of Parsing: Remaining operators are popped from the stack and added to the postfix string. - / is popped and added.- ~ is popped and added. |
2 x ln * 4 * 2 5 x * sin / ~ |
[] |
Final Postfix Expression:
2 x ln * 4 * 2 5 x * sin / ~
./src/polish_calc
The function int polish_calc(char const *const postfix, double *const result)
is designed to calculate the value of an expression written in postfix (Reverse Polish) notation. It takes a string with a postfix expression and the address of a double
variable where the result will be stored. It returns one of the error codes:
1
: No error-1
: Division by zero-2
: Square root of a negative number-3
: Logarithm of a non-positive number-4
: Unknown operator-5
: Unknown function
-
Stack Initialization:
- A stack
operandStack
is created and initialized to store operands.
- A stack
-
Tokenizing the String:
- The string is split into tokens using spaces as delimiters with
strtok
.
- The string is split into tokens using spaces as delimiters with
-
Processing Tokens:
-
Numbers:
- If a token starts with a digit, it is converted to
double
usingatof
and pushed onto the operand stack.
- If a token starts with a digit, it is converted to
-
Unary Minus (
~
):- The top operand is popped from the stack, negated, and the result is pushed back onto the stack.
-
Binary Operators (
+
,-
,*
,/
,^
):- The two top operands are popped from the stack.
- The corresponding operation is applied using the function
apply_operator
. - The result of the operation is pushed back onto the stack if no errors occur.
-
Functions:
- If a token is a known function, the top operand is popped, the function is applied using
apply_function
, and the result is pushed back onto the stack. - If the function is unknown, the error code
ERROR_UNKNOWN_FUNCTION
is set.
- If a token is a known function, the top operand is popped, the function is applied using
-
-
Retrieving the Final Result:
- If no errors occurred, the final result is popped from the stack and stored in the variable
result
. - The error code is returned.
- If no errors occurred, the final result is popped from the stack and stored in the variable
./src/graph/
The module plot_graph.c
is intended for displaying the graph of mathematical functions in the console. Graphs are plotted based on postfix expressions (Reverse Polish Notation), which are evaluated for various values of the variable x
. The results are visualized in the console using text symbols.
-
int plot_graph(char const *const postfixExpression)
- Main Function to initiate the graph plotting process.
- Takes a postfix expression representing a mathematical function of
x
and outputs its graph in the console. - Default Ranges:
x_min = -20
,x_max = 20
y_min = -10
,y_max = 10
- Generating Points:
- Calls the function
gen_points
to compute graph points based on the expression and thex
range. - Receives an array of points and the number of computed points.
- Calls the function
- Drawing the Graph:
- Iterates through each row (
y
) and column (x
) of the console withinGRAPH_HEIGHT
andGRAPH_WIDTH
. - For each position, calls
graph_draw_cns
, which determines which symbol to display at that position.
- Iterates through each row (
- Displaying Ranges: After plotting the graph, displays the
x
andy
value ranges. - Memory Cleanup: Frees the memory allocated for the array of points.
- Return Value: Returns
1
upon successful execution.
-
Point *gen_points(char const *const postfixExpression, int const x_min, int const x_max, int *const num_points)
- Generates an array of points (
Point
) for the graph based on the given expression andx
range. - Calculating Step Size for the
x
Axis: Determines how muchx
changes at each step based on the graph's width and thex
range. - Initializing Counter: Sets the initial number of points (
*num_points
) to0
. - Calculating Values:
- For each step in
x
:- Computes the specific value
x_value
based on the current step. - Calls
evaluate_postfix_expression
to calculate the correspondingy_value
, replacing all occurrences ofx
with the currentx_value
. - If the calculation is successful (
ERROR_NONE
), stores the point in the array and increments the point counter.
- Computes the specific value
- For each step in
- Returning the Array: Returns a pointer to the array containing the calculated points.
- Generates an array of points (
-
void graph_draw_cns(char const *const postfixExpression, int const x_min, int const x_max, int const y_min, int const y_max, int const x, int const y)
- Responsible for drawing a specific symbol at the position
(x, y)
in the console, representing both the coordinate axes and the function itself. - Creates a visual representation of the graph by determining which symbol should be displayed at each position.
- Calculates Scaling:
- Determines
x_scale
andy_scale
to convert coordinates from mathematical to console.
- Determines
- Determines Axes:
- Calculates the positions
y_zero
andx_zero
for they
andx
axes, respectively.
- Calculates the positions
- Determines the Symbol:
- If the position coincides with the intersection of the axes, sets the symbol
'+'
. - If the position is on the
y
axis, sets the symbol'|'
. - If the position is on the
x
axis, sets the symbol'-'
.
- If the position coincides with the intersection of the axes, sets the symbol
- Displays the Function:
- Calculates the function value for the current
x
. - Determines the
y
position for this value. - If the
y
position is close to the currenty
position in the console (consideringDELTA
), sets the symbol'*'
.
- Calculates the function value for the current
- Responsible for drawing a specific symbol at the position
-
char *replace_x_with_value(char const *const str, double const value)
- Replaces the variable
x
in the string expression with a specific numerical valuevalue
.
- Replaces the variable