| CS 261 | University of Puget Sound | Spring, 2020 |
|
Computer Science II
|
||
|
Abstract Data Types and their Implementations,
Some Basic Algorithms,
Object-oriented Problem Solving, and Efficiency |
||
Warning:
This course is under development.
Although the basic structure of this course is largely established,
nothing on this Web site should be considered official or even possibly
correct.
DO NOT MAKE PLANS BASED ON THE CONTENTS OF THIS SITE UNTIL JANUARY, 2020.
An important theme that pervades much problem solving with computing involves the managing of complexity. Often computing applications seek to address problems and difficulties of people; computing offers the possibility of making a community a better place. However, when addressing societal challenges, the problems posed often are large and complex. In this context, a natural question is, "how can a programmer or a development team organize their work to handle complexity?"
In practice, the general field of software engineering provides insights and proposes approaches. A full study of software engineering requires extensive study and thus is far beyond the scope of this course. However, two related topics fit nicely within our current context: levels of abstraction and Abstract Data Types (ADTs).
Still another issue relates to how much data should we store in various structures. Also, to what extent should we store data once, referring to it (e.g., via pointers) elsewhere in a program, and to what extent should we make multiple copies of the data.
The notes that follow expand somewhat upon these three points. Discussion of levels of abstraction builds upon previous work throughout the course. Later work in this module explores ADTs and copies of data in additional depth.
One fundamental approach for managing complexity involves limiting the amount of detail that a developer must handle at one time. Various capabilities are organized into levels of abstraction, each drawing upon work developed at lower levels.
As an illustration, consider programming done within this course.
At the lowest level, hardware performs various tasks, based on circuits, binary instructions, and data in binary form.
The operating system works directly with the hardware to accomplish common tasks. Here are a few examples:
When a program imports a library package, such
as ArrayList or System.out, the programmer
relies upon appropriate array-access and printing operations —
the programmer does not need to focus on specific details in the
ArrayList structure or writing process. (What really
happens when a laser printer moves paper and places toner on a page
to create images and text? Fortunately, such details are handled
behind the scenes at a low level of abstraction, and the user need
not devote time or effort to that work.)
User programs utilize import statements to build upon
previously-defined libraries. In some cases (e.g., with linked lists), a
user may define separate libraries that can be used with multiple projects
and programs.
With all of this work, software development can focus upon some elements of an overall application at a time. Once low-level capabilities are available, higher-level work can use the relevant functions and structures. No need to worry about various details at high levels, once the implementations are properly completed.
Overall, we can manage much complexity by dividing an overall project into largely-independent levels and then developing the capabilities of each level separately.
The following list identifies common levels of abstraction, working from high-level applications downward toward the hardware.
Courses on computer organization and operating systems typically refine such a hierarchy substantially, reinforcing these general levels of abstraction.
Another approach for managing complexity involves grouping data together with the operations that process that data. Such a logical, operational unit is called an Abstract Data Type, abbreviated ADT. That is, an Abstract Data Type or ADT contains two parts:
Further, in describing an ADT, the user interface is considered as being separate from the implementation details. As an example, we might abstract the properties of a list as involving the creation of a list, insertion and deletion of elements, retrieval of one or more elements, and possible rearrangement or altering of elements. To define a ListADT, we would need to carefully describe the user's perspective in a header file, and then an implementation file would supply details.
From the standpoint of an ADT, the user interface identifies the logical data types and describes what the operations do. The specification of an ADT describes behavior — what is to be done, but not how.
A separate implementation file provides the working code behind the scenes. Sometimes, if processing seems slow with one implementation of an ADT, the behind-the-scenes details may change dramatically for efficiency. However, an application using the ADT could remain unchanged — utilizing a new implementation without changing the interface or application code in any way.
The readings and labs in this module describe two common ADTs: stacks and queues. In both cases, a header file specifies the data type and associated operations. Also, in both cases, we will explore one implementation involving an array behind the scenes, and another implementation involving linked lists. With either implementation, the user interface will be the same, but the coding details will be quite different.
An Abstract Data Type is typically defined in two parts:
.h file) specifies the logical data
types and associated operations on those data.
.java file) that supply the
coding details for the user interface.
The stack and queue are two of the most common ADTs — widely used throughout computing systems and applications.
Since ADTs serve as independent elements within a software project, one person or team can work on underlying implementation details while others utilize the specified interface. This separation of effort provides an important tool in managing complexity.
Throughout this module, we will be passing data into a new structure (a stack or a queue) and then retrieving the data in a particular way. Within this framework, we might store a reference to the original data in a new structure, or we could store a copy of the original data. Several arguments on each side are possible:
Store a reference to the original data.
Store a copy of the data.
The project at the end of this module reviews both of these approaches (storing references and storing copies) and explores examples illustrating various consequences.
In a business, having a single record for each employee — perhaps including references in various applications — facilitates consistency. When the employee moves to a new address or opens a new account for deposit of a pay check, updating one record allows all applications to access the new information.
In an art studio, a photographer may wish to make several variations of an original picture. Before creating a new variation, the photographer copies the original, so the new, creative image will not erase any earlier images.
|
created 10 April 2016 by Henry M. Walker revised 15 April 2016 by Henry M. Walker minor editing 1 February 2020 by Henry M. Walker |
|