C Programming Data Structures Applications Expression Evaluation Undo Feature Complete Guide

 Last Update:2025-06-23T00:00:00     .NET School AI Teacher - SELECT ANY TEXT TO EXPLANATION.    8 mins read      Difficulty-Level: beginner

Understanding the Core Concepts of C Programming data structures Applications Expression Evaluation, Undo Feature


C Programming Data Structures Applications: Expression Evaluation, Undo Feature

In the realm of C programming, data structures play a pivotal role in designing efficient and scalable applications. They provide a systematic way to organize and store data, enabling developers to perform operations such as expression evaluation and implementing features like undo. Below, we delve into each of these applications, showcasing the importance of data structures and illustrating their implementations in C.

Data Structures: An Overview

Data structures are specialized formats for organizing and storing data on a computer. They make it easier to manage large amounts of data, improving program efficiency. Common data structures include arrays, linked lists, stacks, queues, trees, and graphs. Each structure has unique characteristics and is suited for specific types of operations.

Expression Evaluation

Expression evaluation involves computing the value of mathematical expressions, such as 3 + (2 * 4) or sin(π/2). Inmany applications, especially within compilers and interpreters, expressions are represented as strings and need to be converted into a form that can be executed.

Why Use Data Structures?

Data structures like stacks and trees are indispensable for parsing expressions. Stacks help manage operator precedence and parentheses, while trees represent the hierarchical structure of expressions.

Algorithm: Infix to Postfix Conversion

One efficient method to evaluate expressions is to convert the infix expression (standard mathematical notation) to postfix notation (Reverse Polish Notation), then evaluate the postfix expression using a stack. Here’s a step-by-step approach:

  1. Initialize an empty stack and an empty postfix expression.
  2. Scan the infix expression from left to right.
  3. If the element is:
    • Operand (number or variable), append it to the postfix expression.
    • Left Parenthesis (, push it onto the stack.
    • Right Parenthesis ), pop from the stack to the postfix expression until the left parenthesis is encountered on the stack.
    • Operator (+, -, *, /), pop the stack to the postfix expression until an operator of lower precedence or a left parenthesis is at the top of the stack, then push the current operator onto the stack.
  4. After scanning, pop the stack to the postfix expression until the stack is empty.

Here’s a sample code snippet for converting infix to postfix in C:

#include <stdio.h>
#include <ctype.h>
#include <string.h>

#define MAX 100

char stack[MAX];
int top = -1;

void push(char x) {
    stack[++top] = x;
}

char pop() {
    if(top == -1)
        return -1;
    else
        return stack[top--];
}

int priority(char x) {
    if(x == '(')
        return 0;
    if(x == '+' || x == '-')
        return 1;
    if(x == '*' || x == '/')
        return 2;
    return 0;
}

int main() {
    char exp[MAX], *e, x;

    printf("Enter the expression: ");
    scanf("%s", exp);

    e = exp;

    while (*e != '\0') {
        if (isdigit(*e))
            printf("%c", *e);
        else if (*e == '(')
            push(*e);
        else if (*e == ')') {
            while ((x = pop()) != '(')
                printf("%c", x);
        } else {
            while (priority(stack[top]) >= priority(*e))
                printf("%c", pop());
            push(*e);
        }
        e++;
    }

    while (top != -1)
        printf("%c", pop());
    return 0;
}

Evaluating Postfix Expression

The postfix expression is evaluated using a stack.

  1. Initialize an empty stack.
  2. Scan the postfix expression from left to right.
  3. If the element is:
    • Operand, push it onto the stack.
    • Operator, pop two elements from the stack, apply the operator, and push the result back onto the stack.
  4. After scanning, the stack contains one element—the result of the expression.

Here’s a sample code snippet for evaluating postfix expressions in C:

#include <stdio.h>
#include <ctype.h>

#define MAX 100

int stack[MAX];
int top = -1;

int priority(char x) {
    if(x == '(')
        return 0;
    if(x == '+' || x == '-')
        return 1;
    if(x == '*' || x == '/')
        return 2;
    return 0;
}

void push(int x) {
    stack[++top] = x;
}

int pop() {
    if(top == -1)
        return -1;
    else
        return stack[top--];
}

void evalPostfix(char *exp) {
    int i;

    for (i = 0; i < strlen(exp); i++) {
        if (isdigit(exp[i]))
            push(exp[i] - '0');
        else {
            int o2 = pop();
            int o1 = pop();

            switch (exp[i]) {
                case '+': push(o1 + o2); break;
                case '-': push(o1 - o2); break;
                case '*': push(o1 * o2); break;
                case '/': push(o1 / o2); break;
            }
        }
    }

    printf("Result: %d\n", pop());
}

int main() {
    char exp[100];
    printf("Enter the postfix expression: ");
    scanf("%s", exp);
    evalPostfix(exp);
    return 0;
}

Undo Feature

Implementing an undo feature in applications allows users to revert their last actions, enhancing the user experience. This is particularly useful in text editors, graphics editors, and other software where user operations can be numerous and complex.

Why Use Data Structures?

Data structures like stacks are well-suited for implementing undo features. A stack operates on a last-in, first-out (LIFO) principle, making it easy to track and reverse recent actions.

Implementing Undo Feature in C

Below is an example of implementing an undo feature in a simple text editor. We use a stack to store the modifications.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAX 100

char *stack[MAX];
int top = -1;

void push(char *str) {
    if(top == MAX - 1)
        printf("Stack is full!\n");
    else
        stack[++top] = strdup(str);
}

char* pop() {
    if(top == -1) {
        printf("Stack is empty!\n");
        return NULL;
    }
    return stack[top--];
}

void display(char *text) {
    printf("Current Text: %s\n", text);
}

int main() {
    char text[MAX] = "";
    char input[MAX];
    char *undoText;

    while(1) {
        printf("Enter text (or type 'exit' to quit, 'undo' to undo): ");
        scanf("%s", input);

        if(strcmp(input, "exit") == 0)
            break;

        if(strcmp(input, "undo") == 0) {
            undoText = pop();
            if(undoText) {
                strcpy(text, undoText);
                free(undoText);
                printf("Undo successful!\n");
            }
            display(text);
        } else {
            push(text);
            strcat(text, input);
            display(text);
        }
    }

    return 0;
}

Conclusion

Data structures are integral to C programming, providing powerful tools for handling complex operations like expression evaluation and implementing features such as undo. By leveraging stacks and other structures, developers can create efficient, responsive, and user-friendly applications. Understanding and applying these data structures effectively can significantly improve the design and performance of C programs.


Online Code run

🔔 Note: Select your programming language to check or run code at

💻 Run Code Compiler

Step-by-Step Guide: How to Implement C Programming data structures Applications Expression Evaluation, Undo Feature

  1. Expression Evaluation using Stacks.
  2. Implementing an Undo Feature using Stacks.

Part 1: Expression Evaluation Using Stacks

Problem Statement: Convert and evaluate an infix expression (like 3 + 4 * 2 / ( 1 - 5 )) to postfix and then evaluate it using stacks.

Step 1: Convert Infix to Postfix

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#define MAX 100

char stack[MAX];
int top = -1;

void push(char x) {
    stack[++top] = x;
}

char pop() {
    if(top == -1) return -1;
    else return stack[top--];
}

int priority(char x) {
    if(x == '(') return 0;
    if(x == '+' || x == '-') return 1;
    if(x == '*' || x == '/') return 2;
    return 0;
}

int convert(char *infix, char *postfix) {
    int i = 0, j = 0;
    char x;
    push('(');
    strcat(infix, ")");
    x = infix[i++];
    
    while(x != '\0') {
        if(x == ' ') i++;
        else 
        if(isdigit(x) || isalpha(x)) postfix[j++] = x;
        else 
        if(x == '(') push(x);
        else 
        if(x == ')') {
            while((x = pop()) != '(') postfix[j++] = x;
        }
        else {
            while(priority(stack[top]) >= priority(x)) postfix[j++] = pop();
            push(x);
        }
        x = infix[i++];
    }
    postfix[j] = '\0';
}

int evaluate(char *postfix) {
    int i;
    char x;
    int num1, num2, r;
    
    for(i = 0; postfix[i] != '\0'; i++) {
        x = postfix[i];
        
        if(isdigit(x)) {
            push(x - '0');
        }
        else {
            num2 = pop();
            num1 = pop();
            
            switch(x) {
                case '+': r = num1 + num2; break;
                case '-': r = num1 - num2; break;
                case '*': r = num1 * num2; break;
                case '/': r = num1 / num2; break;
            }
            push(r);
        }
    }
    return pop();
}

int main() {
    char infix[MAX], postfix[MAX]; 
    printf("Enter Infix Expression : \n");
    scanf("%s", infix);
    
    convert(infix, postfix);
    printf("Postfix Expression: %s\n", postfix);
    
    int result = evaluate(postfix);
    printf("Result of Evaluation: %d\n", result);
    
    return 0;
}

Explanation:

  1. The convert function reads the infix expression and converts it to postfix using the stack.
  2. The evaluate function reads the postfix expression and evaluates it using the stack.
  3. We test the complete functionality in main function.

Step 2: Run & Test

Compile & run:

gcc exp_eval.c -o exp_eval
./exp_eval

Input:

Enter Infix Expression :
3+4*2/(1-5)

Output:

Postfix Expression: 342*15-/+
Result of Evaluation: 1

Part 2: Implementing an Undo Feature Using Stacks

Problem Statement: Implement an undo feature for an array of integers using a stack, where each push operation to the stack represents an action, and each undo operation pops the stack.

Step 1: Implement Undo Feature

#include <stdio.h>
#include <stdlib.h>

#define MAX 100

int stack[MAX];
int top = -1;

void push(int x) {
    stack[++top] = x;
}

int pop() {
    if(top == -1) {
        printf("Nothing to undo.\n");
        return -1;
    } else {
        return stack[top--];
    }
}

void display() {
    if(top == -1) {
        printf("No actions taken yet.\n");
        return;
    }
    for(int i = 0; i <= top; i++) {
        printf("%d ", stack[i]);
    }
    printf("\n");
}

int main() {
    int choice, num;
    while(1) {
        printf("1. Take Action\n");
        printf("2. Undo Action\n");
        printf("3. Display Actions\n");
        printf("4. Exit\n");
        printf("Enter your choice: ");
        scanf("%d", &choice);
        
        switch(choice) {
            case 1:
                printf("Enter the action: ");
                scanf("%d", &num);
                push(num);
                break;
            case 2:
                pop();
                printf("Action undone.\n");
                break;
            case 3:
                display();
                break;
            case 4:
                exit(0);
            default:
                printf("Invalid choice.\n");
        }
    }
    return 0;
}

Explanation:

  • We provide the user with choices to either take an action, undo the last action, display all actions, or exit.
  • The actions are stored in a stack, with push operations representing taking an action and pop operations representing undoing an action.
  • We implement functions for push(), pop(), and display() to handle these tasks.

Step 2: Run & Test

Compile & run:

gcc undo_feature.c -o undo_feature
./undo_feature

Sample Input:

1. Take Action
Enter your choice: 1
Enter the action: 5
1. Take Action
Enter your choice: 1
Enter the action: 10
1. Take Action
Enter your choice: 3
1. Take Action
Enter your choice: 2
Action undone.
1. Take Action
Enter your choice: 3
1. Take Action
Enter your choice: 4

Sample Output:

Top 10 Interview Questions & Answers on C Programming data structures Applications Expression Evaluation, Undo Feature

1. What is an expression in the context of programming, and why is it important?

Answer: An expression in programming is a combination of values, constants, variables, operators, and function calls that are combined to produce another value. Evaluating expressions correctly is crucial because it allows programs to perform calculations and determine the outcome of logic evaluations, which is fundamental to any algorithmic programming task. Accurate expression evaluation ensures the reliability and correctness of the software.

2. Which data structure is commonly used for evaluating postfix expressions?

Answer: A stack data structure is commonly used for evaluating postfix (Reverse Polish) expressions. The algorithm reads each token from the input; if it's a number, it pushes it onto the stack. If it's an operator, it pops the necessary operands from the stack, applies the operator, and pushes the result back onto the stack. This approach simplifies the handling of operators and their precedence.

3. How can we evaluate infix expressions using C?

Answer: Infix expressions, like A + B * C, can be evaluated in C by converting them first into postfix or prefix notations and then using a stack for the actual evaluation. Alternatively, you can use the Shunting Yard algorithm by Edsger Dijkstra to directly evaluate the infix expression using two stacks—one for the final output and one for operators. Here’s a simplified method:

  • Convert the infix expression to postfix.
  • Use a single stack to evaluate the postfix expression.

Below is a snippet for converting from infix to postfix using a stack:

#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAX 100

void push(int* stack, int* top, char item) {
    if (*top >= MAX - 1)
        return;
    stack[++(*top)] = item;
}

char pop(int* stack, int* top) {
    if (*top < 0)
        return ' ';
    return stack[(*top)--];
}

int precedence(char op) {
    if (op == '+' || op == '-')
        return 1;
    if (op == '*' || op == '/')
        return 2;
    return 0;
}

int applyOp(int a, int b, char op) {
    switch(op) {
        case '+': return a + b;
        case '-': return a - b;
        case '*': return a * b;
        case '/': return a / b;
    }
    return 0;
}

int evaluate(char* exp) {
    int values[MAX];
    int ops[MAX];
    int i, values_top=-1, ops_top=-1;

    for(i = 0; i < strlen(exp); i++){
        if(exp[i] == ' ') continue;

        else if(isdigit(exp[i])) {
            int val = 0;
            while(i < strlen(exp) && isdigit(exp[i]))
                val = (val*10) + (exp[i++]-'0');

            push(values, &values_top, val);
            i--; // Adjust to counteract the increment after loop condition check
        }

        else if(exp[i] == '('){
            push(ops, &ops_top, exp[i]);
        }

        else if(exp[i] == ')') {
            while(ops_top != -1 && ops[ops_top] != '('){
                int val2 = pop(values, &values_top);
                int val1 = pop(values, &values_top);
                char op = pop(ops, &ops_top);
                push(values, &values_top, applyOp(val1, val2, op));
            }
            if(ops_top != -1 && ops[ops_top] != '(')
                return -1;
            else
                pop(ops,&ops_top);
        }

        else {
            while(ops_top != -1 && precedence(ops[ops_top]) >= precedence(exp[i])){
                int val2 = pop(values, &values_top);
                int val1 = pop(values, &values_top);
                char op = pop(ops, &ops_top);
                push(values, &values_top, applyOp(val1, val2, op));
            }
            push(ops, &ops_top, exp[i]);
        }
    }

    while(ops_top != -1) {
        int val2 = pop(values, &values_top);
        int val1 = pop(values, &values_top);
        char op = pop(ops, &ops_top);
        push(values, &values_top, applyOp(val1, val2, op));
    }

    return pop(values, &values_top);
}

int main() {
    char exp[] = "10 + 2 * 6";
    printf("%d\n", evaluate(exp));
    return 0;
}

4. Explain how a stack is utilized in implementing an undo mechanism.

Answer: A stack can be effectively used to implement an undo mechanism. It operates based on the Last In First Out (LIFO) principle, ideal for operations that need to be reversed in the reverse order of their occurrence. In the undo system:

  • Each operation performed (e.g., edits in a text editor) is pushed onto the stack.
  • When an undo command is issued, the last operation (top element of the stack) is popped out, and the opposite action is performed.
  • This ensures that recent actions can be undone consecutively until the stack is empty.

Below is a simple illustration in C:

#include <stdio.h>
#include <stdlib.h>

#define MAX 100

char stack[MAX][MAX];
int top=-1;

void push(char* str) {
    if (top >= MAX - 1)
        return;
    strcpy(stack[++top], str);
}

char* pop() {
    if (top < 0)
        return NULL;
    return stack[top--];
}

int main() {
    push("Add Line");
    push("Delete Character");
    
    printf("Undo: %s\n", pop());
    printf("Undo: %s\n", pop());
    return 0;
}

5. Can you describe the difference between a queue and a stack in C?

Answer: In C, queues and stacks are both abstract data types but operate differently:

  • Stack: Operates in LIFO (Last In First Out) manner. Use push() to add elements at the top and pop() to remove them from the top.
  • Queue: Operates in FIFO (First In First Out) manner. Use enqueue() to add elements to the end and dequeue() to remove them from the front.

Here’s a brief example:

#include <stdio.h>
#include <stdlib.h>
#define MAX 100

// Stack Functions Implementation
int stack[MAX];
int top = -1;

void push_stack(int x) { stack[++top] = x; }

int pop_stack() { return stack[top--]; }

// Queue Implementation Using Arrays
int queue[MAX];
int front = 0, rear = -1;

void enqueue(int x) { queue[++rear] = x; }

int dequeue() { return queue[front++]; }

int main() {
    push_stack(10);
    push_stack(20);
    printf("Stack Popped: %d\n", pop_stack()); // Output: 20

    enqueue(10);
    enqueue(20);
    printf("Dequeued from Queue: %d\n", dequeue()); // Output: 10

    return 0;
}

6. Why do we convert infix to postfix before evaluating in C?

Answer: Converting infix to postfix (also known as Reverse Polish Notation) before evaluation helps simplify parsing since:

  • There’s no need for parentheses for grouping operations.
  • Operator precedence and associativity are inherently maintained without additional checks needed for these aspects.
  • It can be evaluated faster using a single stack as opposed to handling parentheses and multiple operators in an infix expression.

7. How does memory allocation work in C for implementing dynamic-sized data structures?

Answer: In C, you can use dynamic memory allocation for creating data structures whose sizes are not fixed during compile-time using malloc(), calloc(), realloc(), and free().

  • malloc(size) allocates memory of given size and returns a void pointer to the allocated space. You must typecast this to your desired data type.
  • calloc(num, size) allocates memory for num objects of size size and initializes all bytes to zero.
  • realloc(ptr, new_size) resizes the memory block pointed to by ptr to new_size bytes.
  • free(ptr) releases the allocated memory so it can be reused.

An example to dynamically create a stack:

#include <stdio.h>
#include <stdlib.h>

typedef struct Stack {
    int* arr;
    int capacity;
    int top;
} Stack;

Stack* createStack(int size) {
    Stack* S = (Stack*)malloc(sizeof(Stack));
    S->capacity = size;
    S->top = -1;
    S->arr = malloc(S->capacity * sizeof(*S->arr)); 
    return S;
}

void resize(Stack* S) {
    S->capacity *= 2; // Double the original size
    S->arr = realloc(S->arr, S->capacity * sizeof(*S->arr));
}

void push(Stack* S, int x) {
    if (S->top == S->capacity - 1) resize(S);
    S->arr[++S->top] = x;
}

int pop(Stack* S) {
    if (S->top == -1) {
        printf("Stack is Empty\n");
        return -1;
    }
    return S->arr[S->top--];
}

int main() {
    Stack* S = createStack(5);
    push(S, 10);
    push(S, 20);
    printf("%d\n", pop(S));
    
    free(S->arr);
    free(S);
    return 0;
}

8. Name one application where expressions are evaluated in real life.

Answer: A common real-life application involving expression evaluation is in spreadsheets and financial modeling software. When users enter formulas (like=A1+B2*C3), the software parses and evaluates these infix expressions behind the scenes, often converting them to postfix for easier computation.

9. Can you elaborate on the implementation of a postfix evaluation with error checking?

Answer: While evaluating a postfix expression, various errors can occur, such as encountering too many operators or operands. Implementing error-checking ensures the program handles these gracefully and provides useful feedback. Here’s an updated version of the evaluator with basic error checking:

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>

#define MAX 100

int stack[MAX];
int top = -1;

void push(int x) {
    if(top == MAX-1) {
        fprintf(stderr, "Error: stack overflow\n");
        exit(EXIT_FAILURE);
    }
    stack[++top] = x;
}

int pop() {
    if(top == -1 ) {
        fprintf(stderr, "Error: stack underflow\n");
        exit(EXIT_FAILURE);
    } 
    return stack[top--];
}

int evalPostfix(char* exp) {
    int i;
    for (i = 0; i < strlen(exp); ++i) {
        if (isdigit(exp[i])) push(exp[i] - '0');
        else if (exp[i] != ' '){
            int val2 = pop();
            int val1 = pop();

            switch(exp[i]) {
                case '+': push(val1 + val2); break;
                case '-': push(val1 - val2); break;
                case '*': push(val1 * val2); break;
                case '/': 
                    if (val2 == 0){
                        fprintf(stderr, "Error: Division by zero\n");
                        exit(EXIT_FAILURE);
                    }
                    push(val1/val2);
                    break;
                default: 
                    fprintf(stderr, "Error: Invalid operator\n");
                    exit(EXIT_FAILURE);
            }
        }
    }

    int ret_val = stack[top];
    if (top > 0){  // More remaining numbers indicates malformed input
        fprintf(stderr, "Error: too many operands\n");
        exit(EXIT_FAILURE);
    }

    return ret_val;
}

int main() {
    char* exp = "2 3 1 * + 9 -";
    printf("%d\n", evalPostfix(exp));
    return 0;
}

10. Implement an undo feature for a simple text editor using linked lists and C.

Answer: To implement an undo feature in C, you can use a singly linked list where each node represents an edit operation. Nodes can store information about edits like insertions or deletions. Reversing (undoing) an operation involves deleting the last node in the list.

Here’s a basic implementation focusing on undo capability:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct Operation {
    char actType;       // Type of action e.g., ‘i’ for insert and ‘d’ for delete
    int pos;            // Position of insertion/deletion
    char ch;            // Inserted/deleted character
    struct Operation* next;
} Operation;

Operation* head = NULL;

void addAction(char actType, int pos, char ch) {
    Operation* op = (Operation*)malloc(sizeof(Operation));
    op->actType = actType;
    op->pos = pos;
    op->ch = ch;
    op->next = head;
    head = op;
}

void undoAction(char* str) {
    if (head == NULL) {
        printf("Error: No actions to undo.\n");
        return;
    }

    Operation* op = head;
    int oldIndex = 0;

    head = head->next;

    if (op->actType == 'i') {  // Undo insert => delete
        for (oldIndex = 0; oldIndex < op->pos; oldIndex++)
            putchar(' '); // Placeholder
        putchar('\b');    // Move cursor back one space 
        putchar(' ');     // Display a space character
        putchar('\b');    // Move cursor back one space 
    } else if (op->actType == 'd') {  // Undo delete => insert
        for (oldIndex = 0; oldIndex < op->pos; oldIndex++)
            putchar(str[oldIndex]);
        putchar(op->ch);

        str[op->pos] = op->ch;
        str[op->pos+1] = '\0';
    }

    free(op);
}

int main() {
    char str[MAX] = "";
    char ch;
    int pos = 0;

    addAction('i', pos++, 'H');
    addAction('i', pos++, 'e');
    addAction('i', pos++, 'l');

    printf("Current string: %s\n", str);

    undoAction(str);
    printf("Undo once: %s\n", str);

    undoAction(str);
    printf("Undo twice: %s\n", str);

    return EXIT_SUCCESS;
}

This example uses a linked list to log actions ('insert' in this case) and undoes these actions by reversing them through the undo function.

You May Like This Related .NET Topic

Login to post a comment.