Stacks

Defining a Stack as an Abstract Data Type

A stack is an example of a linear data structure. Linear data structures are collections of components arranged in a straight line. When we add or remove components of linear data structures, they grow and shrink. If we restrict the growth of a linear data structure so that new components can only be added or removed only at one end, we have a stack.

Stacks are useful data structures and are used in a variety of ways in computer science. You already know that stacks are used to implement functions and you have seen that each running program has a stack and how a program's stack grows and shrinks during calls to functions/procedures. This is especially important in understanding how recursion works.

In general, stacks are useful for processing nested structures or for functions which call other functions (or themselves). A nested structure is one that can contain instances of itself embedded within itself. For example, algebraic expressions can be nested because a subexpression of an algebraic expression can be another algebraic expression. Stacks are used to implement functions, parsers, expression evaluation, and backtracking algorithms.

A pile of books, a stack of dinner plates, a box of pringles potato chips can all be thought of examples of stacks. The basic operating principle is that last item you put in is first item you can take out. That is, that a stack is a Last In First Out (LIFO) structure.

As an abstract entity, a stack is defined by the operations of adding items to the stack, push(), and the operation of removing items from the stack, pop(). There are a couple of other incidentals, that we need to take care of such as not push()ing an item onto a full stack and not trying to pop() items from an empty stack. This Abstract Data Type definition of a stack does not define how a stack is implemented in a computer program, it only defines the operations on a stack. In general, an Abstract Data Type (ADT) consists of a collection of data structures together with a set of operations defined on those data structures. We are already familiar with the structuring methods that C provides, like arrays, structs, and strings. To define an ADT, in addition to defining the basic data structures, we need to define a set of permissible operations on those data structures. In the case of stacks we need to define a structure (probably using struct)) to hold the data, as well as functions for manipulating the data, namely push(), pop() and two functions to check whether a stack is full() or empty().

An ADT for a stack may therefore look like:

A stack, S of type T is a sequence of items of type T on which the following operations are defined:

1. Initialize the stack S to be the empty stack
2. Determine whether or not the stack S is empty()
3. Determine whether or not the stack S is full()
4. push() a new item of type T onto the top of the stack, S
5. If S in not empty, pop() an item from the top of the stack, S.

Implementing a Stack

Many things need to be kept in mind when implementing an abstract data type, or more generally, when implementing something that will be used by many people. There are two points of view to cater to. We usually want to keep the implementation hidden from the user so that if we (as implementers) think of a better, more efficient design we can implement this better design without the user's knowledge. From the user's point of view, I don't care how you've implemented a stack, all I care about is that I can easily use your implementation of a stack.

The two points of view, ease of use, ease of implementation, generality, and robustness are some of the criteria that led to the following implemenation of the Stack ADT.

From the implementer's point of view we need to implement the following functions.

1. Stack * initstack(void) which "creates" and initializes stack, S of type Stack and returns a pointer to S. If it is not possible to create a stack, it returns a pointer to NULL.

2. int empty(Stack *S) which returns true if the stack, S, is empty and false otherwise.

3. int full(Stack *S) which returns true if the stack, S, is full and false otherwise.

4. int push(ItemType X, Stack *S) which pushes X on the stack, only if S is not full. Success or failure is indicated by the integer return value. A return value of 1 indicates sucess, a return value of 0 indicates failure.

5. int pop(Stack *S, ItemType *X) which returns the item popped off the stack in X, only if S is not empty. A return value of 1 indicates sucess, a return value of 0 indicates failure.

6. void stackprint(Stack *S, char *format) which prints the current contents of the stack from the top of the stack to the bottom using the format string that is passed in and which is appropriate for ItemType. It should indicate which is the item at the top of the stack.

We will try and make these functions as general as possible. To do so let us leave the the typedefs for Stack and ItemType in #include files named StackTypes.h to be created by a user, and stacks.h to be created by the implementer.

We thus expect StackTypes.h (created by the user) will contain typedefs for ItemType to be defined by the user and it will also contain a #define to define the maximum number of items that a stack can hold.

Similarly, stacks.h (created by the implementer) will contain typedefs for Stack, function prototyped declarations for the stack functions above, and #defines having to do with the implementation.

Suppose a user is only going to deal with characters, that will be pushed and popped from a stack. The user also realizes that no more than a 100 items will ever be put on the stack. The user has been told that MAXSIZE needs to be the maximum size of the stack and that ItemType must be typedef'd to be the type of the items on a stack. This user can then create a StackTypes.h that looks like:

```#define MAXSIZE 100

typedef char ItemType;
```
Now, to create and use stacks, all the user needs to do is to create a program file that includes StackTypes.h, stacks.h in that order and she/he will be able to invoke the stack functions in this program file.

The implementer's job is now to

1. Design a data structure for a stack and associated data and declare the stack function prototypes in stacks.h and then
2. Declare variables and define the functions that deal with the stack in stack.c
We as implementers have decided that any one user can use upto 10 stacks at a time. Each stack needs space for holding items of type ItemType defined by the user in StackTypes.h. For simplicity, we have decided to use an array of ItemType items of size MAXSIZE to hold stack items. In addition, each stack needs an integer variable that holds the subscript of the current top of the stack (top). Think of top as a kind of "pointer" to the top of the stack. stacks.h then will look like:
```/* must include StackTypes.h before including this file */

#define NSTACKS 10

#ifndef TRUE
#define TRUE (1 == 1)
#endif

#ifndef FALSE
#define FALSE (0 == 1)
#endif

/* typedef for Stack */

typedef struct
{
ItemType items[MAXSIZE];
int top;
} Stack;

/* Prototype declarations */

int empty(Stack *S), full(Stack *S);
Stack * InitStack(void);
int push(ItemType X, Stack *S);
int  pop(Stack *S, ItemType *X);
void PrintStack(Stack *S, char *format);
```
NSTACKS is our (implementer's) upper limit on the number of stacks available. If TRUE and FALSE have not been previously defined we define them. Then comes the typedef for Stack. Note that a Stack contains two members:
1. An array of type ItemType (size MAXSIZE)
2. An integer top, that will be used to "point" to the top of this stack.
Finally, we have prototype declarations for the stack functions. These are taken straight from the "user manual" of stack functions

It is now time to declare the variables we will be using and define the stack functions. We will put these variables and functions in a file called stack.c whose first few lines look like:

```#include <stdio.h>

#include "StackTypes.h"
#include "stacks.h"     /* must be included AFTER StackTypes.h */

static int inuse = 0;
static Stack stacks[NSTACKS];
```
We include stdio.h then StackTypes.h and stacks.h in that order. StackTypes.h must be included before stacks.h because it typedefs ItemType and #defines MAXSIZE which are used in stacks.h

The last two lines declare static global variables. inuse counts the number of stacks currently in use (maximum is NSTACKS). stacks is an array of structures of type Stack and the size of this array is NSTACKS. The array declares NSTACK stacks that can be usd to hold items of type ItemType. These static variables cannot be accessed outside this file.

With these declarations ready, we can think about defining InitStack(). This function returns a pointer to a newly created stack, if one is available. Otherwise it returns the NULL pointer. The idea behind implementing InitStack() is to take a pointer to one of the unused NSTACK stacks, and return it to the invoking function after setting the variable top to 0. If all NSTACK stacks are in use, return NULL.

```Stack * InitStack(void)
{
if (inuse < NSTACKS)
{
stacks[inuse].top = 0;
inuse++;
return &(stacks[inuse - 1]);
}
else
{
return NULL;
}
}
```
Note that we have to return &(stacks[inuse - 1]). This is because we have already incremented inuse to reflect the new number of stacks in use.

The functions empty() and full() are fairly simple. Given a pointer to a stack structure, check if the member top is 0 or MAXSIZE

```int empty(Stack *S)
{
if (S->top <= 0)
return TRUE;
else
return FALSE;
}

int full(Stack *S)
{
if (S->top >= MAXSIZE)
return TRUE;
else
return FALSE;
}
```

Push() and pop() are also fairly simple. We have made both robust by checking whether a stack is full or empty before we push or pop an item. If we cannot push or pop an item, these functions return the error value of 0, otherwise they do their job and return 1 to indicate success. Since the return value is used to indicate success or failure, pop() must use a pointer to a variable of type ItemType to "return" the item popped of the stack.

```int push(ItemType X, Stack *S)
{
if (full(S))
{
return 0;
}
else
{
S->items[S->top++] = X;
return 1;
}
}

int pop (Stack *S, ItemType *X)
{
if(empty(S))
{
return 0;
}
else
{
*X = S->items[--(S->top)];
return 1;
}
}
```
The post-increment and pre-decrement operators ensure that the right stack top pointer is incremented correctly after pushing an item on the stack, and decremented correctly before popping an item from the stack.

One last function StackPrint() remains to be defined. In our quest for generality, we will try to make StackPrint() work no matter what ItemType item we print. This is accomplished by making StackPrint() take two arguments: the stack to be printed and a format string that corresponds to ItemType.

```void StackPrint(Stack *S, char *format)
{
int i;
printf("top-> ");
for(i = S->top - 1; i >= 0;  i--)
printf(format, S->items[i]);
printf(" <-bot\n");
}
```
That is it. As implementers we have done our job! Let's now switch jobs and become a user.

To use stacks, we only need to provide a type definition for ItemType and figure out what we think the maximum number of items we would store on a stack (put this in StackTypes.h). Then to use the stack functions we would need to include StackTypes.h and stacks.h and we would be able to invoke the stack functions. Here's an example:

```#include <stdio.h>
#include "StackTypes.h"
#include "stacks.h"

.....

int main(int argc, char * argv[])
{
....
ItemType foo;

Stack *S1, *S2;

....

S1 = InitStack();
if (S1 == NULL)
{
printf("Could not create a stack\n");
exit(1);
}

.....

if (push(foo, S) != 1)
{
printf("cannot push on full stack...\n);
take appropriate error action
}

....

if (pop(S, &foo) != 0)
{
printf("cannot pop from empty stack...\n);
take appropriate error action
}

.....

PrintStack(S, "%c  "); /* assuming typedef char ItemType */

.....

}
```