Corning Community College CSCS2320 Data Structures ~~TOC~~ ======Project: DLS0====== =====Errata===== This section will document any updates applied to the project since original release: * __revision 1__: logic error fix and output tweaks to various unit tests (20141105) * logic error in **unit-cpstack**, preventing most of its tests from running (FIXED) * changed ending while to do-while loop * loop conditions were originally (i == 0) && (status != 0), should have been != -1 for both * missing double newline after certain "should be" outputs in **unit-pop** (FIXED) * Some additional aesthetic enhancements to the base Makefile were made. * __revision 2__: Makefile enhancements to facilitate project usage (20141107) * test reference implementation feature enhancements * a slight change to the option to enable (**make use-test-reference**) * a new option to restore your own code (**make use-your-own-code**) * **src/stack/Makefile** now cleans up vim .swp and nano .save files * **testing/stack/unit/Makefile** fixes an off-by-one pretty display spacing issue * __revision 3__: verify-stack.sh enhancements (20141119) * now includes absolute totals =====Objective===== In this project, resume our conceptual journey and explore another data structure: stacks. =====Background===== A **stack** is considered one of the most important data structures, along with **queues** (next week's project) and trees. And it is largely because of how often we find them playing out in nature or our day-to-day lives. The word "stack" is [[https://www.google.com/search?&q=define%3Astack&ie=utf-8&oe=utf-8|defined]] as: * (generically): a pile of objects, typically one that is neatly arranged * (computing): a set of storage locations that store data in such a way that the most recently stored item is the first to be retrieved Additionally, when viewing it as a verb (an action), we also find some positive computing application (bolded) in a less reputable cardplaying usage: * shuffle or **arrange** (a deck of cards) dishonestly **so as to gain** an unfair **advantage** Or, to distill it out: * arrange so as to gain advantage Combining with our previous definitions, we have: * a set of storage locations that are arranged in such a way so as to give us an advantage- the most recently stored item (the last to be placed onto the stack) is the first to be retrieved. ====Lists and Nodes==== So, how does all this list and node stuff play into our stack implementation? Well, we're going to build the stack ON TOP OF lists (which are composed of nodes). Therefore, a stack is a data structure that stores its data in a list (which consists of nodes), and we apply various rules/restrictions on our access of that list data. The concept of restricting access is a very important one- which we did with our list as well (limiting our access to the list through the use of **append()**, **insert()**, and **obtain()** versus manipulating the next/prev pointers manually all the time). By limiting how we access the data, we give ourselves certain algorithmic advantages: * __error reduction__: if have a small set of operations that can do one thing, and do their one thing extremely well (**insert()**, **append()**, and **obtain()** again, for instance), we can then rely on them to do the low-level grunt work, freeing us up to accomplish higher level tasks (such as **sorting** or **swapping**), or even things like determining if a word is a **palindrome**. * __performance__: by restricting our available choices, the edge cases we have to check for are reduced, and in ideal situations, the average case moves closer to the best case. ====conceptualizing a stack==== It is common to think of a stack as a vertical object, much like a pile of papers that need to be processed (or a pile of anything we need to work with). Although we've commonly viewed lists horizontally (from left to right), there is absolutely nothing requiring this positional orientation. Similarly, stacks possess no mandatory orientation, but we do usually visualize them as vertical entities, largely because that's how the piles of paper that accumulate on our desks tend to grow. ====the stack==== The stack data structure presents certain advantages that encourages its use in solving problems (why do we stack a bunch of papers all in the same place to create piles? Why is that more advantageous than giving each one its own unique desk space?), and we accomplish that by its compositional definition: * a stack has a **top**, basically a node pointer that constantly points to the top node in the stack (equivalent to the underlying list's start or end pointer-- you can decide which one you want to use). * to put an item on the stack, we **push** it there. So one of the functions we'll be implementing is **push()**, which will take the node we wish to place on the given stack, and push will handle all the necessary coordination with its underlying list. * to get an item off of the stack, we **pop** it. In our **pop()** function, we grab the **top** node off the stack (this also translates into a set of list-level transactions that our **pop()** function will handle for us). These qualities cause the stack to be described as a LIFO (or FILO) structure: * **LIFO**: **L**ast **I**n **F**irst **O**ut * **FILO**: **F**irst **I**n **L**ast **O**ut And that describes what is conceptually going on-- if we can ONLY access our data through one location (the top), the data most immediately available to us is that which we most recently placed there (hence the last one we pushed in would be the first one we get back when popping it). This concept is very important, and being aware of it can be of significant strategic importance when going about solving problems (and seeing its pattern proliferate in nature). With that said, the existence of **top**, along with the core **push()** and **pop()** functions defines the minimal necessary requiments to interface with a stack. Sometimes we'll see additional actions sneak in. While these may be commonly associated with stacks, they should not be confused as core requiments of a stack: * **peek**: the ability to gain access to the top node without removing it from the stack * **is the stack empty?**: the ability to query the stack and determine if it is empty or non-empty (or perhaps if non-empty, how full is it?) While we may be implementing these supplemental functions, it should be noted that not only are they in no way necessary for using a stack, they could be detrimental (just as relying on **qty** in the sll projects was), as one could rely on them as a crutch. Their inclusion should ONLY be viewed as a means of convenience (in certain scenarios they may result in less code needing to be written), but NOT as something you should routinely make use of. ====size can matter==== With a stack, there sometimes exists a need to cap its total size (especially in applications on the computer, we may have only allocated a fixed amount of space and cannot exceed it). For this reason, we will need to maintain a count of nodes in the stack (ie the underlying list). For this reason, **qty** makes an appearance once again in the list struct. Additionally, the stack will have a configured maximum size- if the quantity of nodes in the list exceeds the configured size of the stack, we should prevent any additional pushes. It should also be pointed out that in other applications, a stack need not have a maximum size.. in which case it can theoretically grow an indefinite amount. We will explore both conditions (unbounded and bounded) in this project. ====stack error conditions==== There are two very important operational error conditions a stack can experience: * __stack **over**flow__: this is the situation where the quantity of the list is equal to the configured stack size, and we try to push another node onto the stack. * __stack **under**flow__: this is the situation where the stack is empty, yet we still try to pop a value from it. =====Project Overview===== For this project, we're going to be implementing the stack data structure atop of our recently re-implemented linked list (the doubly linked list). ====In inc/list.h==== There is one change (an addition) that has been made to **list.h**, and that is the return of the **qty** element: struct list { Node *start; // pointer to start of list Node *end; // pointer to end of list int qty; // holds current count of nodes }; typedef struct list List; // because we deserve nice things It should be noted (and stressed) that the utilization of **qty** should JUST be in maintaining a count. You need not re-implement or change the logic of any of your list functionality to base its operations or to rely on **qty** in any way (just as before, if you do you'll lost credit). From the list's point of view, its sole purpose is for show. With that said, you likely only have to make changes to 4 functions in order to appropriately accommodate **qty**-- any low-level list operation that is responsible for initializing, adding, or taking nodes out of the list. ====In inc/stack.h==== #ifndef _STACK_H #define _STACK_H #include "list.h" // stack relies on list to work // (which relies on node) struct stack { List *data; // pointer to list containing data Node *top; // pointer to node at top of stack int size; // maximum stack size (0 is unbounded) }; typedef struct stack Stack; // because we deserve nice things Stack *mkstack(int ); // create new stack (of max size) Stack *cpstack(Stack * ); // create a copy of an existing stack Stack *rmstack(Stack * ); // clear and de-allocate an existing stack int push(Stack **, Node * ); // put new node on top of stack int pop (Stack **, Node **); // grab (and disconnect) top node off stack Node *peek(Stack * ); // grab (do not disconnect) top node off stack int isempty(Stack * ); // determine if the stack is empty or not #endif For our stack implementation, we will make increased used of the double pointer, in order to achieve passing parameters by address. This is necessary so that we can free up the return value of **push()** and **pop()** to be used for status (ie look out for stack overflows and underflows). peek() and isEmpty() are being implemented as an exercise to aid in your understanding of stacks. Again, avoid their use except is a means of convenience (or to further optimize your code). The general rule of thumb is that the use of peek() and isEmpty() should result in shortening your code in a clear or clever way. If you cannot think of how to solve a problem without the use of peek()/isEmpty(), that is a strong clue that you shouldn't be using them. Also, while nothing is stopping you from doing so, the idea here is that things like **size** and the underlying list **qty** in stack transactions will **NOT** be accessed outside of the push() and pop() functions. Just like my warnings about using **qty** in your list solutions-- do not consider **size** as a variable for your general use. In object-oriented programming, both **size** and **qty** would be **private** member variables of their respective classes, unable to be used by anything other than their respective member functions. ====list library==== Againt, in **src/list/**, you are to add support for **qty** so that, just as the list's **start** and **end** maintain an accurate positioning of their respective aspects of the list, **qty** maintains a count of the total number of nodes still in the list. ===display() enhancements=== You are also to implement 4 additional modes to the display() function. This means you'll need to expand the current modulus divide by 4 to one of 8. The current modes are as follows: // modes: 0 display the list forward, no positional values // 1 display the list forward, with positional values // 2 display the list backwards, no positional values // 3 display the list backwards, with positional values The new modes are (you may want to add these comments to your **display.c** file): // 4 display the list forward, no positional values, in ASCII // 5 display the list forward, with positional values, in ASCII // 6 display the list backwards, no positional values, in ASCII // 7 display the list backwards, with positional values, in ASCII What has changed? In modes 4-8, instead of displaying the numeric value contained in the node's data element, you are instead to represent it as its ASCII character. For example, if there was a numeric 65 stored in the node , in the mode 4-7 representation, an 'A' should instead be displayed. ==display() output examples based on mode== Let's say we have a list with the following elements: start -> 51 -> 49 -> 51 -> 51 -> 55 -> NULL ==mode 0: forward, no positions, as numbers== 51 -> 49 -> 51 -> 51 -> 55 -> NULL ==mode 1: forward, with positions, as numbers== [0] 51 -> [1] 49 -> [2] 51 -> [3] 51 -> [4] 55 -> NULL ==mode 4: forward, no positions, in ASCII== '3' -> '1' -> '3' -> '3' -> '7' -> NULL ==mode 5: forward, with positions, in ASCII== [0] '3' -> [1] '1' -> [2] '3' -> [3] '3' -> [4] '7' -> NULL ====stack library==== In **src/stack/**, you will find skeletons of the above prototyped functions, hollowed out in anticipation of being made operational. Figure out what is going on, the connections, and make sure you understand it. Again, your stack is to utilize the stack for its underlying data storage operations. This is what the stack's **data** list pointer is to be used for. ====Reference Implementation==== As the layers and complexities rise, narrowing down the source of errors becomes increasingly important. If your stack push() isn't working, is it because of a problem in push()? Or might it be in an underlying list operation it relies upon? Or perhaps even the lowest-level node functions.. To aid you in your development efforts, you now have the ability to import a functioning node and list implementation into your project for the purposes of testing your stack functionality. This also provides another opportunity for those who have fallen behind to stay current, as they can work on this project without having needed to successfully get full list functionality. ===Using the test reference implementation=== You'll notice that, upon running **make help** in the base-level Makefile, the following new options appear (about halfway in the middle): ** ** ** make use-test-reference - use working implementation object files ** ** make use-your-own-code - use your node/list implementation code ** ** ** In order to make use of it, you'll need to run **make use-test-reference** from the base of your **dls0** project directory, as follows: lab46:~/src/data/dls0$ make use-test-reference make[1]: Entering directory '/home/wedge/src/data/dls0/src' make[2]: Entering directory '/home/wedge/src/data/dls0/src/list' rm -f *.swp *.o append.o cp.o display.o insert.o mk.o obtain.o rm.o search.o sort.o swap.o core make[2]: Leaving directory '/home/wedge/src/data/dls0/src/list' make[2]: Entering directory '/home/wedge/src/data/dls0/src/node' rm -f *.swp *.o cp.o mk.o rm.o core make[2]: Leaving directory '/home/wedge/src/data/dls0/src/node' make[2]: Entering directory '/home/wedge/src/data/dls0/src/stack' rm -f *.swp *.o cp.o isempty.o mk.o peek.o pop.o push.o rm.o core make[2]: Leaving directory '/home/wedge/src/data/dls0/src/stack' make[1]: Leaving directory '/home/wedge/src/data/dls0/src' make[1]: Entering directory '/home/wedge/src/data/dls0/testing' make[2]: Entering directory '/home/wedge/src/data/dls0/testing/list' make[3]: Entering directory '/home/wedge/src/data/dls0/testing/list/unit' rm -f *.swp *.o ../../../bin/unit-append unit-cplist unit-display unit-findnode unit-insert unit-mklist unit-obtain unit-rmlist unit-sortlist unit-swapnode core make[3]: Leaving directory '/home/wedge/src/data/dls0/testing/list/unit' make[2]: Leaving directory '/home/wedge/src/data/dls0/testing/list' make[2]: Entering directory '/home/wedge/src/data/dls0/testing/node' make[3]: Entering directory '/home/wedge/src/data/dls0/testing/node/unit' rm -f *.swp *.o ../../../bin/unit-cpnode unit-mknode unit-rmnode core make[3]: Leaving directory '/home/wedge/src/data/dls0/testing/node/unit' make[2]: Leaving directory '/home/wedge/src/data/dls0/testing/node' make[2]: Entering directory '/home/wedge/src/data/dls0/testing/stack' make[3]: Entering directory '/home/wedge/src/data/dls0/testing/stack/app' rm -f *.swp *.o ../../../bin/palindrome-stack core make[3]: Leaving directory '/home/wedge/src/data/dls0/testing/stack/app' make[3]: Entering directory '/home/wedge/src/data/dls0/testing/stack/unit' rm -f *.swp *.o ../../../bin/unit-cpstack unit-isempty unit-mkstack unit-peek unit-pop unit-push unit-rmstack core make[3]: Leaving directory '/home/wedge/src/data/dls0/testing/stack/unit' make[2]: Leaving directory '/home/wedge/src/data/dls0/testing/stack' make[1]: Leaving directory '/home/wedge/src/data/dls0/testing' NODE and LIST reference implementation in place, run 'make' to build everything. lab46:~/src/data/dls0$ You'll see that final message indicating everything is in place (it automatically runs a **make clean** for you), and then you can go ahead and build everything with it: lab46:~/src/data/dls0$ make ... **__Debugging__:** When using the test reference implementation, you will not be able to debug the contents of the node and list functions (the files provided do not have debugging symbols added), so you'll need to take care not to step into these functions (it would be just like stepping into **printf()**. You can still compile the project with debugging support and debug (as usual) those compiled functions (ie the stack functions). ===Reverting back to using your code=== If you were trying out the reference implementation to verify stack functionality, and wanted to revert back to your own code, it is as simple as: lab46:~/src/data/dls0$ make use-your-own-code Local node/list implementation restored, run 'make clean; make' to build everything. lab46:~/src/data/dls0$ ====List Library unit tests==== As a result of the required changes to **display()** and the incorporation of **qty**, pertinent list unit tests will be enhanced, so you can make use of them to ensure implementation compliance. Be sure to run the various list unit tests and verification scripts to see which functions have fallen out of compliance with the list struct specification changes issued in this project. The **verify-list.sh** script can be especially useful in getting a big picture view of what work is needed. ====Stack library unit tests==== In **testing/stack/unit/**, you will find these files: * **unit-mkstack.c** - unit test for **mkstack()** library function * **unit-cpstack.c** - unit test for **cpstack()** library function * **unit-rmstack.c** - unit test for **rmstack()** library function * **unit-push.c** - unit test for **push()** library function * **unit-pop.c** - unit test for **pop()** library function * **unit-peek.c** - unit test for **peek()** library function * **unit-isempty.c** - unit test for **isempty()** library function There are also corresponding **verify-FUNCTION.sh** scripts that will output a "MATCH"/"MISMATCH" to confirm overall conformance with the pertinent stack functionality. These are complete runnable programs (when compiled, and linked against the stack library, which is all handled for you by the **Makefile** system in place). Of particular importance, I want you to take a close look at: * the source code to each of these unit tests * the purpose of these programs is to validate the correct functionality of the respective library functions * follow the logic * make sure you understand what is going on * ask questions to get clarification! * the output from these programs once compiled and ran * analyze the output * make sure you understand what is going on * ask questions to get clarification! ====stack testing applications==== ===palindrome-stack=== Now that we've completed our stack functionality, we can use these individual functions to piece together solutions to various everyday problems where a stack could be effective (and even compare approaches to when we didn't have the benefit of a stack in solving the problem). After all, that's a big aspect to learning data structures- they open doors to new algorithms and problem solving capabilities. Our task (once again) will be that of palindromes (ie words/phrases that, when reversed, spell the same thing). This implementation will be considered an extra credit opportunity, so as to offer those who have fallen behind (but working to get caught up) a reprieve on some of the credit they've lost. It is also highly recommended to undertake as it will give you further experience working with these concepts. Note this is a DIFFERENT approach than you would have taken in the program with sll2- you're to use stack functionality to aid you with the heavy lifting. While you will still make use of list functionality for grabbing the initial input, the actual palindrome comparison processing needs to heavily involve stacks. =====Expected Results===== To assist you in verifying a correct implementation, a fully working implementation of the node, list, and stack libraries should resemble the following (when running the respective verify script): ====node library==== Here is what you should get for node: lab46:~/src/data/dls0$ bin/verify-node.sh ==================================================== = Verifying Doubly-Linked Node Functionality = ==================================================== [mknode] Total: 5, Matches: 5, Mismatches: 0 [cpnode] Total: 6, Matches: 6, Mismatches: 0 [rmnode] Total: 2, Matches: 2, Mismatches: 0 ==================================================== [RESULTS] Total: 13, Matches: 13, Mismatches: 0 ==================================================== lab46:~/src/data/dls0$ As no coding changes were needed in the node library, these results should be identical to that of a fully functioning node implementation from the **dll0** project. ====list library==== Here is what you should get for list: lab46:~/src/data/dls0$ bin/verify-list.sh ==================================================== = Verifying Doubly-Linked List Functionality = ==================================================== [mklist] Total: 6, Matches: 6, Mismatches: 0 [cplist] Total: 30, Matches: 30, Mismatches: 0 [rmlist] Total: 3, Matches: 3, Mismatches: 0 [append] Total: 22, Matches: 22, Mismatches: 0 [insert] Total: 22, Matches: 22, Mismatches: 0 [obtain] Total: 23, Matches: 23, Mismatches: 0 [display] Total: 10, Matches: 10, Mismatches: 0 [findnode] Total: 11, Matches: 11, Mismatches: 0 [sortlist] Total: 6, Matches: 6, Mismatches: 0 [swapnode] Total: 7, Matches: 7, Mismatches: 0 ==================================================== [RESULTS] Total: 140, Matches: 140, Mismatches: 0 ==================================================== lab46:~/src/data/dls0$ Due to the re-introduction of **qty** into list (impacting actions performed by **mklist()**, **append()**, **insert()**, and **obtain()**), along with feature additions to **display()**, those functions saw additional tests performed, so our original max total of **102** from **dll0** has now changed to **140** (ie various **qty** and **display()**-related tests adding to the previous total). Remember though- aside from the minor change of adding **qty** and enhancing **display()**, all remaining list functions need no change (and **mklist()/append()/insert()/obtain()** remained largely unchanged). ====stack library==== Here is what you should get for stack: lab46:~/src/data/dls0$ bin/verify-stack.sh =================================================== = Verifying Doubly-Linked Stack Functionality = =================================================== [mkstack] Total: 3, Matches: 3, Mismatches: 0 [cpstack] Total: 8, Matches: 8, Mismatches: 0 [rmstack] Total: 3, Matches: 3, Mismatches: 0 [push] Total: 30, Matches: 30, Mismatches: 0 [pop] Total: 25, Matches: 25, Mismatches: 0 [peek] Total: 24, Matches: 24, Mismatches: 0 [isempty] Total: 13, Matches: 13, Mismatches: 0 =================================================== [RESULTS] Total: 106, Matches: 106, Mismatches: 0 =================================================== lab46:~/src/data/dls0$ =====Submission Criteria===== To be successful in this project, the following criteria must be met: * Project must be submit on time, by the posted deadline. * Late submissions will lose 25% credit per day, with the submission window closing on the 4th day following the deadline. * All code must compile cleanly (no warnings or errors) * all requested functions must be implemented in the related library * all requested functionality must conform to stated requirements (either on this project page or in comment banner in source code files themselves). * Executed programs must display in a manner similar to provided output * output formatted, where applicable, must match that of project requirements * Processing must be correct based on input given and output requested * Output, if applicable, must be correct based on values input * Code must be nicely and consistently indented (you may use the **indent** tool) * Code must be commented * Any "to be implemented" comments **MUST** be removed * these "to be implemented" comments, if still present at evaluation time, will result in points being deducted. * Sufficient comments explaining the point of provided logic **MUST** be present * Track/version the source code in a repository * Submit a copy of your source code to me using the **submit** tool (**make submit** will do this) by the deadline.