CS140 Lecture notes -- Stacks

  • Jim Plank
  • Directory: ~cs140/www-home/notes/Stacks
  • Lecture notes: http://www.cs.utk.edu/~cs140/notes/Stacks
  • Fri Oct 2 10:40:06 EDT 1998

    Stacks

    Ok -- here's our first real data structure -- a stack. A stack is a special kind of linked data structure. It has a special property called LIFO, which means ``last in, first out.'' Basically, a stack has three main operations: Of course, Stack is a (void *).

    Now, these operations are defined in stack.h, which is in the directory /home/cs140/spring-2004/include. To use them, you must link your code with /home/cs140/spring-2004/objs/stack.o.

    Stacksimp.c shows a very simple example of using a stack. First, we push three integers on the stack -- 1, 2 and 3. Then we call stack_pop() twice, and print out the values. Finally, we push 4 onto the stack, and call stack_pop() twice more, printing out the values.

    Here's the code:

    #include < stdio.h >
    #include "jval.h"
    #include "stack.h"
    
    main()
    {
      Jval jv;
      Stack s;
    
      s = new_stack();
    
      stack_push(s, new_jval_i(1));
      stack_push(s, new_jval_i(2));
      stack_push(s, new_jval_i(3));
    
      jv = stack_pop(s);
      printf("First pop: %d\n", jv.i);
      jv = stack_pop(s);
      printf("Second pop: %d\n", jv.i);
      
      stack_push(s, new_jval_i(4));
    
      jv = stack_pop(s);
      printf("Third pop: %d\n", jv.i);
      jv = stack_pop(s);
      printf("Fourth pop: %d\n", jv.i);
    }
    
    (Note you can do this without declaring jv -- see simp2.c).

    And here's its output:

    UNIX> stacksimp
    First pop: 3
    Second pop: 2
    Third pop: 4
    Fourth pop: 1
    UNIX>
    
    So, let's look at this in detail. You can envision a stack as a list of items, where items are added to (pushed) and taken from (popped) the same end of the list.

    Thus, after the first call to stack_push(), the stack s is the list (1). After the second call, s is (1, 2), and after the third call, s is (1, 2, 3).

    Now, the first call to stack_pop() removes the last element in s: 3. Then the s is (1, 2). The second call removes the element 2, and the list becomes (1). Thus, to this point, the output is:

    UNIX> stacksimp
    First pop: 3
    Second pop: 2
    
    Now, we call stack_push(s, new_jval_i(4)), which puts 4 at the end of s. S is now (1, 4). The third call to stack_pop() removes the 4, and s is once again (1). Finally, the last call to stack_pop() removes and returns the 1, and the stack is empty. Thus, the output following the third call to stack_pop() is:
    Third pop: 4
    Fourth pop: 1
    UNIX>
    

    The full stack interface

    Here are all of the procedures defined in stack.h: If you call stack_pop() or stack_top() on an empty stack, your program will exit with an error.

    Two more examples of using the stack data structure

    The first such example is once again reversing the lines of standard input. In the last lecture, we did this by defining our own linked data structure that acted pretty much like a stack. Here, we'll simply use a stack. We'll read lines in from standard input and push them onto a stack. Then when standard input is done, we'll pop lines off the stack and print them. This will print them in reverse order.

    Here's the code (in stackrev.c). Note how we use the jval to hold a string. Also note how convenient new_jval_s() is.

    #include < stdio.h >
    #include < string.h >
    #include "jval.h"
    #include "stack.h"
    #include "fields.h"
    
    main()
    {
      IS is;
      Stack s;
      Jval j;
    
      s = new_stack();
      is = new_inputstruct(NULL);
    
      while (get_line(is) >= 0) {
        stack_push(s, new_jval_s(strdup(is->text1)));
      }
    
      while (!stack_empty(s)) {
        j = stack_pop(s);
        printf("%s", j.s);
      }
    }
    
    This runs just like we think it should:
    UNIX> cat input
    Give
    Him
    Six!
    UNIX> stackrev < input
    Six!
    Him
    Give
    UNIX> 
    
    The first three calls to stack_push() create s: ( "Give\n", "Him\n", "Six!\n" ). The first call to stack_pop removes the "Six!\n" and turns s into: ( "Give\n", "Him\n" ). The second call removes "Him\n", and the third removes "Give\n". At that point, s is empty, and therefore stack_empty(s) returns 1 and the while loop is exited.

    The second example is implementing tail with a stack. What we do here is again read all of standard input into a stack. Then we call stack_size() to see how many lines are in the stack. We want to print out the smaller of the number of lines in the stack, and the first command line argument to tail. In other words, if we call ``tail 10'' and there are 5 lines in standard input, then we want to print out 5 lines. If there are 15 lines in standard input, then we want to print out 10 lines.

    Once we know how many lines we want to print (call it nl), we call stack_pop() nl times. This will get the last nl lines of standard input. The problem is that it gets them backwards. We can't just call stack_pop and then printf(), because that would give us the last nl lines in reverse order. Therefore, we must pop them and put them into an array, and then print them by traversing the array in the correct order.

    This is done in stacktail.c:

    #include < stdio.h >
    #include < string.h >
    #include "jval.h"
    #include "stack.h"
    #include "fields.h"
    
    main(int argc, char **argv)
    {
      IS is;
      Stack *s;
      Jval j;
      char **lines;
      int nl, i;
    
      /* Get the number of lines */
    
      if (argc != 2) {
        fprintf(stderr, "usage: stacktail n\n");
        exit(1);
      }
      nl = atoi(argv[1]);
      if (nl <= 0) exit(0);
    
      /* Intialize stack and input */
    
      s = new_stack();
      is = new_inputstruct(NULL);
    
      /* Push each line onto the stack */
    
      while (get_line(is) >= 0) {
        stack_push(s, new_jval_s(strdup(is->text1)));
      }
    
      /* Set nl to be the min of the given nl and 
         the number of lines in standard input. */
    
      if (stack_size(s) < nl) nl = stack_size(s);
      if (nl == 0) exit(0);
      
      /* Allocate an array to hold nl lines, and pop the last nl lines 
        off the stack into that array.  We put them into the array in 
        reverse order so that the array is in the right order.  */
    
      lines = (char **) malloc(sizeof(char *)*nl);
      for (i = 0; i < nl; i++) {
        j = stack_pop(s);
        lines[nl-i-1] = j.s;
      }
    
      /* Print out the lines by traversing the array forwards */
    
      for (i = 0; i < nl; i++) {
        printf("%s", lines[i]);
      }
    }
    
    And of course, it works like you think it would:
    UNIX> stacktail 2 < input
    Him
    Six!
    UNIX> stacktail 5 < input
    Give
    Him
    Six!
    UNIX> stacktail 5 < stacktail.c
      for (i = 0; i < nl; i++) {
        printf("%s", lines[i]);
      }
    }
    
    UNIX>