renaming labs

This commit is contained in:
Jidong Xiao
2025-01-07 17:17:59 -05:00
parent 190e7fd7f4
commit 77eb9240b2
36 changed files with 1547 additions and 1071 deletions

View File

@@ -1,128 +0,0 @@
# Lab 4 — Memory Diagrams and Memory Debugging
## Checkpoint 1
*estimate: 30-45 minutes*
- Form a group of 2 members. If the number of students in the room is not divisible by 2, the graduate TA will approve a single team with 3 members.
- Introduce yourself to your teammate. Share to your teammate something about yourself (e.g. hobbies, sports, favorite music, etc). Learn something new about your teammate (even if you already know the teammate).
- Our TA/mentor will pick 3 code snippets for each student, from the following 5 code snippets. Following the conventions used in Data Structures lecture for memory diagramming, draw a picture of the stack and the heap that result from executing the code snippet. Use a ? to represent uninitialized values.
[Code Snippet 1](fruits.cpp)
[Code Snippet 2](grains.cpp)
[Code Snippet 3](desserts.cpp)
[Code Snippet 4](veggies.cpp)
[Code Snippet 5](protein.cpp)
Show your diagram to your teammate, and ask the teammate to reverse engineer code for your diagram; at the same time, you reverse engineer code for your teammate's diagram.
When everyone has finished writing code for the drawing, discuss your answers as a group. Are differences in the original and reverse engineered code valid alternate answers? Or was something misinterpreted? Critique each others diagrams. What could be improved or drawn more clearly? What statements need to be added to each example to clean up / prevent memory leaks?
**To complete this checkpoint**, present your work to a TA/mentor.
## Checkpoint 2 & 3:
Checkpoints 2 and 3 focus on using a memory debugger. It is highly recommended that you thoroughly read the instructions for Checkpoint 2 and Checkpoint 3 before starting.
Memory debuggers will be a useful tool in many of the assignments this semester, and in C++ development if you work with the language outside of this course. While a traditional debugger lets you step through your code and examine variables, a memory debugger instead reports memory-related errors during execution and can help find memory leaks after your program has terminated. The next time you see a “segmentation fault”, or it works on your machine but not on Submitty, try running a memory debugger!
Read lecture notes [6.7](../../lectures/06_memory#67-memory-debugging), [6.8](../../lectures/06_memory#68-sample-buggy-program), [6.9](../../lectures/06_memory#69-using-dr-memory-httpwwwdrmemoryorg), [6.10](../../lectures/06_memory#610-using-valgrind-httpvalgrindorg), and [6.11](../../lectures/06_memory#610-using-valgrind-httpvalgrindorg), and try to understand the example buggy program code and what DrMemory or Valgrind say about this buggy program. Ask questions if you don't understand the code or if you don't understand the output of DrMemory or the output of Valgrind.
Please download the following 4 files needed for this lab:
[buggy_lab4.cpp](./buggy_lab4.cpp)
[first.txt](./first.txt)
[middle.txt](./middle.txt)
[last.txt](./last.txt)
## Checkpoint 2
*estimate: 20-40 minutes*
For Checkpoint 2 of this lab, we will heavily rely on dynamic memory to find the average and smallest number for a set of data from an input file. You will use a memory debugging tool such as DrMemory or Valgrind to fix memory errors and leaks in buggy_lab4.cpp. Make sure to download the provided .txt files as well.
First take a look at the code inside the identifyMeanAndMin() function. You will notice that the syntax used
throughout the program may be a little different than what you are used to. Try and familiarize yourself with
this syntax before you start working on the assignment. What does this line of code do?
```cpp
*(intArray + *numElements) = readInt;
```
Whats going on inside the *for* loop? If you are stuck on this, ask a mentor or TA for help as
soon as possible and refer back to your lecture notes on pointers and arrays.
Once you have done this, compile the program using:
```console
g++ buggy_lab4.cpp -o buggy_lab4.out -g -Wall
```
Try running the program normally using:
```console
./buggy_lab4.out
```
You will notice that a segmentation fault occurs. Now run this program using either Valgrind or DrMemory. If running using Valgrind, remember to use --leak-check=yes or --leak-check=full.
Your memory debugger should give you more context as to why a segmentation fault is occurring, especially if you have compiled with *-g*. To complete this checkpoint, add or modify code in the areas marked Parts 1, 2, and 3 to resolve all memory errors and leaks. As you are working on this, be sure to also think about the questions asked in Checkpoint 3.
It is highly recommended that you tackle one part at a time. For example, after adding a few lines of code to part 1, you will now receive different memory errors when you recompile and run the program using your memory debugger. Similarly, fixing all memory errors in part 2 will generate different memory errors that should be resolved in part 3.
In part 2 of buggy_lab4.cpp, the goal is to print out the contents of intArray in reverse order, while also calculating the sum of all elements in the array and keeping track of the smallest number encountered. Solutions that attempt to print the contents of the array in a different manner or end up with the wrong
value for the smallest number found or sum won't be accepted.
Note that:
- You are only allowed to modify or add code when asked to. This would be between the comments that
indicate parts 1, 2, and 3 inside buggy_lab4.cpp. Do not modify other parts of the code or create any
helper functions.
- You are not allowed to declare new variables; the ones provided are more than enough. Hint: how do
we create memory on the heap?
- You are not allowed to use additional data structures (like arrays or vectors).
**You will receive no credit if you do not follow the above restrictions.**
**To receive credit for this checkpoint**: Fix buggy_lab4.cpp so that it successfully prints out the average
and smallest number for a given set of data and is free of all memory errors and leaks on your local machine.
Submit buggy_lab4.cpp on Submitty and verify that there are no memory errors there as well and show a
mentor or TA both results. Also explain to a mentor TA what you added or modified in the program to
resolve all memory errors.
## Checkpoint 3
*estimate: 15-25 minutes*
As you work through Checkpoint 2, try and pay close attention the analysis given by DrMemory or Valgrind
and think about the following:
1. How does the output from your memory debugger differ when you compile your program with the -g
flag compared to when you leave it out?
2. How would you rewrite the for loop in part 2 to use the bracket [] operator instead of pointer syntax?
3. For DrMemory users, you would have encountered all of these errors in parts 1, 2, or 3 of Checkpoint 2:
```console
UNITIALIZED READ
UNADDRESSABLE ACCESS
INVALID HEAP ARGUMENT
LEAK
```
4. What do you think each of these errors mean?
5. For Valgrind users, the errors you will have seen are:
```console
Use of uninitialised value
Invalid write
Invalid read
Conditional jump or move depends on uninitialised value(s)
Mismatched free() / delete / delete []
? bytes in ? blocks are definitely lost in loss record ? of ?
```
**To receive credit for this checkpoint**, discuss your answers to these questions with a TA or mentor.
## Checkpoint 4
*remainder of the lab time*
For this checkpoint, you team up with one or two other students from your lab section. Our TA or mentor will pick one problem from the practice packet for you two (or three) to work together as a group. You should not look at the posted solutions. Work as a group (e.g., pair programming!) to come up with a complete solution to the problem.
Once you have completed and proofread and triple-checked your answer, launch your C++ development environment. First, focus on getting it to compile. What basic, minimal testing infrastructure is necessary to get your solution to compile? What small bits of syntax were you missing or incorrect or sloppy? Try to avoid making these mistakes on Thursday's test!
Then, write a simple test case to exercise your solution. Compile and run the code. As time follows, fix bugs in your solution and improve and expand your test cases.
**To complete this checkpoint**, When about 10 minutes remain in the lab, the TA and mentors will circulate through the room to finalize checkoffs. Show them your initial solution, and explain the typos and errors you found and debugged in working through your computer tested solution. Explain your testing & debugging process.

View File

@@ -1,116 +0,0 @@
#include <iostream>
#include <fstream>
#define MAX_ARRAY_SIZE 50
/** IMPORTANT: SOLUTIONS THAT DO NOT FOLLOW BELOW INSTRUCTIONS WILL RECEIVE NO CREDIT
* Do not add or modify code in this file UNLESS ASKED TO!
* You are NOT allowed to declare new variables; use the ones already declared.
* You are NOT allowed to create new helper functions or modify checkCorrectSmallest()
**/
/** DO NOT MODIFY THIS FUNCTION! **/
int checkCorrectSmallest(int* smallestNum, const std::string& filename) {
if (filename == "first.txt" || filename == "middle.txt") {
if (*smallestNum != 1) {return -1;}
}
else if (filename == "last.txt") {
if (*smallestNum != 22) {return -1;}
}
return 1;
}
/** A function that will identify the mean and smallest number
* in a set of data provided that there are at most 50 numbers
* in the file.
*
* @param filename: The name of a file that contains a list of numbers.
*
* Task: Add or modify code in the appropriate sections to fix memory and logic errors
* without using data structures (such as an array or vector) and without using any
* additional memory on the stack.
**/
void identifyMeanAndMin(const std::string& filename) {
int* numElements;
int* sum;
int* smallestNum;
float* avg;
/** PART 1: ADD CODE BELOW **/
/** PART 1: ADD CODE ABOVE **/
*numElements = 0;
*sum = 0;
int readInt;
int* intArray = new int[MAX_ARRAY_SIZE];
std::ifstream input(filename);
while (input >> readInt) {
*(intArray + *numElements) = readInt;
*numElements += 1;
}
std::cout << "Printing the contents of the array in reverse order: ";
/** PART 2: MODIFY CODE BELOW **/
for (int i = MAX_ARRAY_SIZE; i >= -1; i--) {
// If we're at the beginning of the for loop, initalize *smallestNum
// Else, compare *smallestNum to current element in the for loop
if (i == MAX_ARRAY_SIZE) {
*smallestNum = *(intArray + i);
}
else {
if (*smallestNum > *(intArray + i)) {
*smallestNum = *(intArray + i);
}
}
/** PART 2: MODIFY CODE ABOVE **/
*sum += *(intArray + i);
std::cout << *(intArray + i) << " ";
}
std::cout << std::endl;
if (checkCorrectSmallest(smallestNum, filename) == -1) {
std::cout << "ERROR: incorrect value for smallest number" << std::endl;
return;
}
*avg = *sum / float(*numElements);
std::cout << "The average for the set of numbers in " << filename << " is "
<< *avg << " and the smallest number is " << *smallestNum
<< std::endl;
/** PART 3: ADD AND/OR MODIFY CODE BELOW **/
delete intArray;
/** PART 3: ADD AND/OR MODIFY CODE ABOVE **/
}
int main() {
identifyMeanAndMin("first.txt");
std::cout << std::endl;
identifyMeanAndMin("middle.txt");
std::cout << std::endl;
identifyMeanAndMin("last.txt");
return 0;
}

View File

@@ -1,10 +0,0 @@
bool** cake;
bool pie;
bool fudge;
pie = true;
cake = new bool*[5];
cake[1] = &pie;
bool* donut = new bool;
*donut = false;
cake[2] = donut;
cake[4] = &fudge;

View File

@@ -1,4 +0,0 @@
1
2
3
4

View File

@@ -1,10 +0,0 @@
int pear = 3;
int* apple;
int banana[pear];
int* orange;
apple = new int[pear];
orange = &banana[1];
apple[0] = 6;
apple[1] = 7;
apple[2] = 8;
*orange = 5;

View File

@@ -1,10 +0,0 @@
float* oat[3];
oat[1] = new float;
*oat[1] = 3.14;
oat[2] = new float;
*oat[2] = 6.02;
float rice;
float* wheat;
wheat = oat[2];
float** barley = new float*;
*barley = oat[1];

View File

@@ -1,49 +0,0 @@
22
247
279
240
253
342
87
181
391
57
23
302
168
367
236
240
187
368
216
185
31
255
122
140
69
46
287
69
268
58
134
330
172
291
175
63
184
329
30
337
229
274
130
95
255
331
24
325
228

View File

@@ -1,10 +0,0 @@
89
34
13
5
2
1
3
8
21
55

View File

@@ -1,11 +0,0 @@
int tofu = 3;
int chicken = 2;
double** fish = new double*[tofu];
for (int beef = 0; beef < tofu; beef++) {
fish[beef] = new double[chicken];
}
fish[0][0] = 1.41421;
fish[0][1] = 1.61803;
fish[1][0] = 2.71828;
fish[1][1] = 3.14159;
fish[2][0] = 6.02214;

View File

@@ -1,10 +0,0 @@
char*** carrot;
char** broccoli;
char* tomato;
char radish = 'q';
tomato = new char;
*tomato = 'z';
broccoli = new char*;
*broccoli = tomato;
carrot = new char**;
*carrot = broccoli;

View File

@@ -1,218 +0,0 @@
# Lab 5 — Vec Implementation
## Checkpoint 1
*estimate: 20 minutes*
- Team up with one student in your lab section. MEET SOMEONE NEW! You may not work
with someone who was on your team for Lab 4. Ask a TA or mentor to help you find a partner.
If the number of students in the room is not even, the graduate TA will approve a single team with 3
members.
- Introduce yourself to your teammate. Ask them to share something about themselves (e.g. hobbies,
sports, favorite music, etc.) Learn something new about your teammate (even if you already know
them).
- There are two sets of functions below. Each student in your team takes one set and working on it.
For each function below, assign different letters to each of the data sizes that at first glance might have impact
on the running time of the function. Be sure to consider integer value, size of vector, and length of string.
Then give the big O notation of the function in terms of those variables.
### Student 1
```cpp
int foobar (const std::vector<std::string> &a, int b) {
int answer = 0;
for (int i = 0; i < a.size(); i+=b) {
answer++;
}
return answer;
}
```
```cpp
void foo2 (const std::vector<int> &a, std::string &b) {
b.clear();
for (int i = 0; i < a.size(); i++)
{
if (a[i] > 0)
b.push_back('+');
else
b.push_back('-');
}
}
```
```cpp
std::vector<int> foo3 (const std::vector<int> &a, const std::string &b) {
return std::vector<int>(b.size(),a.size());
}
```
```cpp
int foo3 (const std::vector<std::string> &a, const std::string& b) {
int ret = 0;
for (int i=0; i<a.size(); i++){
ret += (a[i] == b);
}
return ret;
}
```
```cpp
std::vector<int> foo4 (const std::vector<int> &a) {
std::vector<int> answer = a;
for (int i = 0; i < a.size(); i++) {
if(a[i] < (a[a.size()-1]*a[a.size()-1])){
answer.erase(answer.end()-1);
}
}
return answer;
}
```
```cpp
std::vector<int> foo5 (const std::vector<int> &a, int b) {
std::vector<int> ret;
for(int i=0; i<a.size(); i++){
if(a[i] < b){
ret.insert(ret.end(),a[i]);
}
}
return ret;
}
```
### Student 2
```cpp
int foobar (const std::vector<std::string> &a, int b) {
int answer = 0;
for (int i = 0; i < a.size(); i+=b) {
answer++;
}
return answer;
}
```
```cpp
std::vector<int> bar2 (const std::vector<std::string> &a) {
std::vector<int> answer;
for (int i = 0; i < a.size(); i++) {
answer.push_back(a[i].size());
}
return answer;
}
```
```cpp
std::vector<std::string> bar3 (const std::vector<int> &a) {
std::vector<std::string> answer;
for (int i = 0; i < a.size(); i++) {
answer.push_back(std::string(a[i],'+'));
}
return answer;
}
```
```cpp
void bar3 (std::vector<std::string> &a, const std::string &b) {
for (int i = 0; i < a.size(); i++) {
a[i] = b;
}
}
```
```cpp
std::vector<int> bar4 (const std::vector<std::string> &a) {
std::vector<int> answer;
if(!a.empty()){
for (int i = 0; i < std::min(a[0].size(), a.size()); i++) {
answer.insert(answer.begin(),a[i].size());
}
}
return answer;
}
```
```cpp
void bar5 (std::vector<int> &a) {
for (int i = 0; i < a.size(); i++){
if (a[i] > 0){
a.erase(a.begin() + i);
i--;
}
}
}
```
When you finish, discuss these problems with your teammate. If your teammate hasnt finished, please help
them (but without just doing the problems for them).
Once you are both finished, type these examples into your C++ editor, add print statements, and confirm your answers are correct. What print statements will be most helpful? In your terminal, instead of running just
```console
./a.out
```
try running
```console
time ./a.out
```
and reading the real time, which is how long your program took to run. How does this change as you increase or decrease each of the data size variables you identified above?
**To complete this checkpoint**, as a team, present your work to a TA/mentor.
## Checkpoint 2
*estimate: 30-40 minutes*
Write a templated non-member function named remove_matching_elements that takes in two arguments,
a vector of type Vec&lt;T&gt; and an element of type T, and returns the number of elements that matched the
argument and were successfully removed from the vector. The order of the other elements should stay
the same. For example, if v, a Vec&lt;int&gt; object contains 6 elements: 11 22 33 11 55 22 and you call
remove_matching_elements(v,11), that call should return 2, and v should now contain: 22 33 55 22.
You should not create a new vector in your function.
Add several test cases to test_vec.cpp to show that the function works as expected. What is the order
notation of your solution in terms of n the size of the vector, and e the number of occurences of the input
element in the vector?
*Note*: when you are in a non-member function, and you want to use the iterator, which is a member type of the Vec&lt;T&gt; class, you have to use the *typename* keyword before the Vec&lt;T&gt;:: scope. For example, if you want to define an iterator named *itr*, you can do it like this:
```console
typename Vec<T>::iterator itr
```
without this *typename* keyword, if you define the iterator *itr* like this:
```console
Vec<T>::iterator itr
```
you will get a compiler error saying:
```console
error: need typename before Vec<T>::iterator because Vec<T> is a dependent scope
```
And the reason that this keyword *typename* is needed, is because without it, the compiler would think that Vec&lt;T&gt;::iterator is a member variable of the Vec&lt;T&gt; class, but this *typename* explicitly tells the compiler that Vec&lt;T&gt;::iterator is a type, rather than a member variable.
**To complete this checkpoint**, show a TA your debugged solution for remove_matching_elements and
be prepared to discuss the order notation of the function.
## Checkpoint 3
*estimate: 30 minutes*
Add a print member function to Vec to aid in debugging. (Note, neither remove_matching_elements nor
print are part of the STL standard for vector). You should print the current information stored in the
variables capacity, m_size, and m_data. Use the print function to confirm your remove_matching_elements
function is debugged. Also, write a test case that calls push_back many, many times (hint, use a for loop!)
and observe how infrequently re-allocation of the m_data array is necessary.
To verify your code does not contain memory errors or memory leaks, use Valgrind and/or Dr. Memory on
your local machine see instructions on the course webpage: Memory Debugging. Also, submit your code
to the homework server (in the practice space for lab 4), which is configured to run the memory debuggers
for this exercise. To verify that you understand the output from Valgrind and/or Dr. Memory, temporarily
add a simple bug into your implementation to cause a memory error or memory leak.
**To complete this checkpoint**, show a TA your tested & debugged program. Be prepared to demo and
discuss the Valgrind and/or Dr. Memory output: with and without memory errors and memory leaks AND
on your local machine and on the homework server.

View File

@@ -1,97 +0,0 @@
#include <iostream>
#include <cmath>
using namespace std;
#include "vec.h"
int main() {
// ---------------------------------------------------
// initialize v1 with 10 values... the multiples of 5
Vec<int> v1( 10, 0 );
int i;
for ( i = 0; i < v1.size(); i++) {
v1[i] = 5 * i;
}
cout << "v1.size() = " << v1.size() << ". Should be 10.\n";
cout << "Contents of v1 (multiples of 5):";
for ( i = 0; i<v1.size(); ++i ) {
cout << " " << v1[i];
}
cout << endl;
// --------------------------------------------------------------------------
// make v2 be a copy of v1, but then overwrite the 2nd half with the 1st half
Vec<int> v2( v1 );
v2[ 9 ] = v2[ 0 ];
v2[ 8 ] = v2[ 1 ];
v2[ 7 ] = v2[ 2 ];
v2[ 6 ] = v2[ 3 ];
v2[ 5 ] = v2[ 4 ];
cout << "Contents of v1 (still multiples of 5):";
for ( i = 0; i<v1.size(); ++i )
cout << " " << v1[i];
cout << endl;
cout << "Contents of v2 (now palindrome):";
for ( i = 0; i<v2.size(); ++i )
cout << " " << v2[i];
cout << endl;
// ------------------------------------------
// make v3 be a copy of v2, but then clear it
Vec<int> v3;
v3 = v2;
v3.clear();
cout << "\nAfter copying v2 to v3 and clearing v3, v2.size() = "
<< v2.size() << " and v3.size() = " << v3.size() << endl;
cout << "Contents of v2 (should be unchanged):";
for ( i = 0; i<v2.size(); ++i ) {
cout << " " << v2[i];
}
cout << endl;
// --------------
// test push back
cout << "\nNow testing push_back. Adding 3, 6, 9 to v2:\n";
v2.push_back( 3 );
v2.push_back( 6 );
v2.push_back( 9 );
cout << "v2 is now: \n";
for ( i = 0; i<v2.size(); ++i ) {
cout << " " << v2[i];
}
cout << endl;
// -----------
// test resize
v1.resize(20,100);
cout << "\nNow testing resize. Resizing v1 to have 20 elements and v2 to have 2 elements\n";
cout << "v1 is now (should have 100s at the end): \n";
for ( i = 0; i<v1.size(); ++i )
cout << " " << v1[i];
cout << endl;
v2.resize(2,100);
cout << "v2 is now: \n";
for ( i = 0; i<v2.size(); ++i )
cout << " " << v2[i];
cout << endl;
// ------------------------
// test of a vec of doubles
cout << "\nStarting from an empty vector, z, of doubles and doing\n"
<< "5 push_backs\n";
Vec<double> z;
for ( i = 0; i<5; ++i )
z.push_back( sqrt( double(10*(i+1)) ));
cout << "Contents of vector z: ";
for (int j = 0; j < z.size(); j++ )
cout << " " << z[j];
cout << endl;
// ADD MORE TEST CASES HERE
return 0;
}

View File

@@ -1,141 +0,0 @@
#ifndef Vec_h_
#define Vec_h_
// Simple implementation of the vector class, revised from Koenig and Moo. This
// class is implemented using a dynamically allocated array (of templated type T).
// We ensure that that m_size is always <= capacity and when a push_back or resize
// call would violate this condition, the data is copied to a larger array.
template <class T> class Vec {
public:
// TYPEDEFS
typedef T* iterator;
typedef const T* const_iterator;
// CONSTRUCTORS, ASSIGNMNENT OPERATOR, & DESTRUCTOR
Vec() { this->create(); }
Vec(int n, const T& t = T()) { this->create(n, t); }
Vec(const Vec& v) { copy(v); }
Vec& operator=(const Vec& v);
~Vec() { delete [] m_data; }
// MEMBER FUNCTIONS AND OTHER OPERATORS
T& operator[] (int i) { return m_data[i]; }
const T& operator[] (int i) const { return m_data[i]; }
void push_back(const T& t);
iterator erase(iterator p);
void resize(int n, const T& fill_in_value = T());
void clear() { delete [] m_data; create(); }
bool empty() const { return m_size == 0; }
int size() const { return m_size; }
// ITERATOR OPERATIONS
iterator begin() { return m_data; }
const_iterator begin() const { return m_data; }
iterator end() { return m_data + m_size; }
const_iterator end() const { return m_data + m_size; }
private:
// PRIVATE MEMBER FUNCTIONS
void create();
void create(int n, const T& val);
void copy(const Vec<T>& v);
// REPRESENTATION
T* m_data; // Pointer to first location in the allocated array
int m_size; // Number of elements stored in the vector
int capacity; // Number of array locations allocated, m_size <= capacity
};
// Create an empty vector (null pointers everywhere).
template <class T> void Vec<T>::create() {
m_data = NULL;
m_size = capacity = 0; // No memory allocated yet
}
// Create a vector with size n, each location having the given value
template <class T> void Vec<T>::create(int n, const T& val) {
m_data = new T[n];
m_size = capacity = n;
for (T* p = m_data; p != m_data + m_size; ++p)
*p = val;
}
// Assign one vector to another, avoiding duplicate copying.
template <class T> Vec<T>& Vec<T>::operator=(const Vec<T>& v) {
if (this != &v) {
delete [] m_data;
this -> copy(v);
}
return *this;
}
// Create the vector as a copy of the given vector.
template <class T> void Vec<T>::copy(const Vec<T>& v) {
this->capacity = v.capacity;
this->m_size = v.m_size;
this->m_data = new T[this->capacity];
// Copy the data
for (int i = 0; i < this->m_size; ++i)
this -> m_data[ i ] = v.m_data[ i ];
}
// Add an element to the end, resize if necesssary.
template <class T> void Vec<T>::push_back(const T& val) {
if (m_size == capacity) {
// Allocate a larger array, and copy the old values
// Calculate the new allocation. Make sure it is at least one.
capacity *= 2;
if (capacity < 1) capacity = 1;
// Allocate and copy the old array
T* new_data = new T[capacity];
for (int i=0; i<m_size; ++i)
new_data[i] = m_data[i];
// Delete the old array and reset the pointers
delete [] m_data;
m_data = new_data;
}
// Add the value at the last location and increment the bound
m_data[m_size] = val;
++ m_size;
}
// Shift each entry of the array after the iterator. Return the iterator,
// which will have the same value, but point to a different element.
template <class T> typename Vec<T>::iterator Vec<T>::erase(iterator p) {
// remember iterator and T* are equivalent
for (iterator q = p; q < m_data+m_size-1; ++q)
*q = *(q+1);
m_size --;
return p;
}
// If n is less than or equal to the current size, just change the size. If n is
// greater than the current size, the new slots must be filled in with the given value.
// Re-allocation should occur only if necessary. push_back should not be used.
template <class T> void Vec<T>::resize(int n, const T& fill_in_value) {
if (n <= m_size)
m_size = n;
else {
// If necessary, allocate new space and copy the old values
if (n > capacity) {
capacity = n;
T* new_data = new T[capacity];
for (int i=0; i<m_size; ++i)
new_data[i] = m_data[i];
delete [] m_data;
m_data = new_data;
}
// Now fill in the remaining values and assign the final size.
for (int i = m_size; i<n; ++i)
m_data[i] = fill_in_value;
m_size = n;
}
}
#endif

View File

@@ -1,101 +0,0 @@
# Lab 6 — Reversing Data 8 Ways: STL Vectors vs. STL Lists vs. Homemade Linked Lists
## Checkpoint 1A: Reverse with STL Vector Swaps
*estimate: TBD*
Read the starter code [checkpoint1.cpp](checkpoint1.cpp) and complete the function *reverse* that reverses the contents of an STL vector of integers. For example, if the contents of the vector are in increasing order before the call to reverse_vector, then they will be in decreasing order afterwards. For this checkpoint, use indexing/subscripting/[] on the vector, not iterators (or pointers). You may not use a second vector or array or list.
The trick is to step through the vector one location at a time, swapping values between the first half of the
vector and the second half. As examples, the value at location 0 and the value at location size()-1 must
be swapped, and the value at location 1 and the value at location size()-2 must be swapped.
Make sure your code works with even and odd length vectors. Also add a few more tests to the main function
to make sure your code will work for the special cases of an empty vector and vectors of size 1 and 2.
## Checkpoint 1B: Reverse with STL List Swaps
*estimate: TBD*
Copy your code from Checkpoint 1A to a new file. Then, convert this code to use STL Lists instead of STL
vectors. Start by replacing vector with list everywhere. And youll need to replace your subscripting
with iterators.
You may want to use a straightforward concept we did not discuss in lecture: a reverse iterator. A reverse
iterator is designed to step through a list from the back to the front. An example will make the main
properties clear:
```cpp
std::list<int> a;
unsigned int i;
for ( i=1; i<10; ++i ){
a.push_back( i*i );
}
std::list<int>::reverse_iterator ri;
for( ri = a.rbegin(); ri != a.rend(); ++ri ){
std::cout << *ri << std::endl;
}
```
This code will print out the values 81, 64, 49, . . . , 1, in order, on separate lines. You can also compile and run this [example program](reverse_iterator.cpp).
Observe the type for the reverse iterator, the use of the functions rbegin and rend to provide iterators that delimit the bounds on
the reverse iterator, and the use of the ++ operator to take one step backwards through the list. It is very
important to realize that rbegin and end are NOT the same thing! One of the challenges here will be
determining when to stop (when youve reached the halfway point in the list). You may use an integer
counter variable to help you do this.
For this checkpoint you should not use *erase*, or *insert*, or the *push* or *pop* functions.
Note, youll probably need to add the keyword typename in front of your templated iterator types to unconfuse the compiler.
```cpp
typename std::list<T>::iterator itr = data.begin();
```
**To complete this checkpoint**, show a TA your debugged functions to reverse STL vectors and STL lists
by element swapping. Be sure to ask your TA/mentors any questions you have about regular vs. reverse
iterators for lists and vectors.
## Checkpoint 2: Reverse with STL List Using Insert/Erase/Push/Pop
*estimate: TBD*
Form a team of 4. You may form a team of 2 or 3 only with approval from your graduate lab TA.
Each student should make a copy of their solution file for Checkpoint 1B. And then, each student should
rewrite their STL list reverse function:
- **STUDENT 1** Using only front(), pop_front(), and insert().
- **STUDENT 2** Using only back(), pop_back(), insert(), and iterator increment.
- **STUDENT 3** Using only erase(), push_front(), and iterator dereference.
- **STUDENT 4** Using only erase(), push_back(), iterator dereference, and iterator decrement.
Each of these solutions is allowed to use a for or while loop and a temporary variable to store a single
element, but should not use any auxiliary data structures (no array, no vector, no additional list, etc.)
Note that these solutions are quite different than the algorithms that reverse a vector or list by swapping
values. If the algorithm is paused and printed midway, the vector contents will be different than the algorithms
for Checkpoint 1. (Go ahead, add more calls to the print function in the middle of your reverse function.) Test
and debug your own code before helping your teammates. Discuss the similarities and differences between
the solutions to each version of the reverse function.
**To complete this checkpoint**, as a team, present your debugged solutions to a TA or mentor.
## Checkpoint 3: Reversing a Homemade Linked List
*estimate: TBD*
This checkpoint is an individual checkpoint. (But you can ask your teammates questions if you get stuck.)
Pull out some paper. Following the conventions from lecture, draw a picture of a “homemade” singly-linked
list that stores the values 1, 2, 3, and 4. Make a variable on the stack named my_list of type Node* that
points to the first node in the chain (the node storing the value 1). The 4 node objects should be separate
blobs of memory dynamically-allocated on the heap.
Now, modify this diagram to reverse the list you can do this by only changing pointers! You should use the
existing node objects. Dont copy the entire diagram or make any new nodes. You should not change the
values inside of any node dont swap values like we did for Checkpoint 1.
Then, write pseudo-code to reverse this list, just changing the pointers as you diagrammed above. You may
use helper variables (of type Node*) but no other data structures or variables. Remember that when we
directly manipulate homemade linked lists we dont use iterators.
Finally, read the starter code of checkpoint 3: [checkpoint3.cpp](checkpoint3.cpp). Complete the reverse function using your diagram and pseudocode as a guide. Test and debug the code. Add a few additional test cases to the main function to ensure your code works with an empty list,
and lists with one or two values. Also add a test or two of a node chain with something other than ints.
If you have time, write 2 versions of this function, one version should be iterative (using a for or while loop)
and one version should be recursive.
**Note**: this reverse function takes a pointer as its argument, but we are passing this pointer by reference, because we want to modify this pointer. To understand the concept of passing a pointer by reference, you are recommended to read and run this [example program](reference_to_a_pointer.cpp).
**To complete this checkpoint**, show a TA or mentor your diagram and your debugged function(s) to
reverse a homemade singly-linked list.

View File

@@ -1,47 +0,0 @@
#include <iostream>
#include <string>
#include <vector>
template <class T>
void print(std::vector<T> &data, const std::string &label) {
std::cout << label << " ";
for (int i = 0; i < data.size(); i++) {
std::cout << " " << data[i];
}
std::cout << std::endl;
}
template <class T>
void reverse(std::vector<T> &data) {
// FILL IN THIS FUNCTION
}
int main() {
std::vector<int> data;
data.push_back(1);
data.push_back(2);
data.push_back(3);
data.push_back(4);
data.push_back(5);
data.push_back(6);
data.push_back(7);
print(data,"before:");
reverse(data);
print(data,"after: ");
std::vector<std::string> data2;
data2.push_back("apple");
data2.push_back("banana");
data2.push_back("carrot");
data2.push_back("date");
print(data2,"before:");
reverse(data2);
print(data2,"after: ");
}

View File

@@ -1,63 +0,0 @@
#include <iostream>
#include <string>
// Note: It's ok that all the member variables are public for this
// tiny class.
template <class T>
class Node {
public:
T value;
Node<T> *ptr;
};
template <class T>
void print(Node<T> *data, const std::string &label) {
std::cout << label;
Node<T> *tmp = data;
while (tmp != NULL) {
std::cout << " " << tmp->value;
tmp = tmp->ptr;
}
std::cout << std::endl;
}
template <class T>
void reverse(Node<T>* &input) {
// FILL IN THIS FUNCTION
}
int main() {
// manually create a linked list of nodes with 4 elements
Node<int>* my_list = new Node<int>;
my_list->value = 1;
my_list->ptr = new Node<int>;
my_list->ptr->value = 2;
my_list->ptr->ptr = new Node<int>;
my_list->ptr->ptr->value = 3;
my_list->ptr->ptr->ptr = new Node<int>;
my_list->ptr->ptr->ptr->value = 4;
my_list->ptr->ptr->ptr->ptr = NULL;
print(my_list,"my_list before");
reverse(my_list);
print(my_list,"my_list after ");
// Note: We are not deleting any of the Nodes we created... so this
// program has memory leaks!
}
// ===========================================================================

View File

@@ -1,28 +0,0 @@
/* This example demonstrates the usage of passing a pointer by reference.
* It is needed when you want to modify the pointer.
*/
#include <iostream>
// function to modify the value of a pointer through reference
void modifyPointer(int* & ptr, int& newValue) {
ptr = &newValue; // assign the address of newValue to the pointer
}
int main() {
int value = 42;
int* ptr = &value;
// print the original value of the pointer
std::cout << "Original value of pointer: " << *ptr << std::endl;
int newValue = 100; // new value to assign to the pointer
// pass the pointer by reference to the function, so that we can change the pointer
modifyPointer(ptr, newValue);
// print the modified value of the pointer
std::cout << "Modified value of pointer: " << *ptr << std::endl;
return 0;
}

View File

@@ -1,18 +0,0 @@
#include <iostream>
#include <list>
int main(){
std::list<int> a;
unsigned int i;
for ( i=1; i<10; ++i ){
a.push_back( i*i );
}
std::list<int>::reverse_iterator ri;
for( ri = a.rbegin(); ri != a.rend(); ++ri ){
std::cout << *ri << std::endl;
}
return 0;
}

View File

@@ -0,0 +1,77 @@
# Lab 7 — List Implementation
## Checkpoint 1
*estimate: 20-30 minutes*
The implementation of the dslist class in [dslist.h](dslist.h) is incomplete. In particular, the class is missing the destroy_list
private member function that is used by the destructor and the clear member function. The provided test
case in [checkpoint1.cpp](checkpoint1.cpp) works “fine”, so whats the problem?
Before we fix the problem, lets use Dr. Memory and/or Valgrind to look at the details more carefully.
You should use the memory debugging tools both on your local machine and by submitting the files to
the homework server. Study the memory debugger output carefully. The output should match your
understanding of the problems caused by the missing destroy_list implementation. Ask a TA if you
have any questions.
Now write and debug the destroy_list function and then re-run the memory debugger (both locally and on
the submission server) to show that the memory problems have been fixed. Also finish the implementation
of the push_front, pop_front, and pop_back functions.
**To complete this checkpoint**, show a TA the implementation and memory debugger output before and
after writing destroy_list.
## Checkpoint 2:
*estimate: 20-30 minutes*
The PushBack() and the PrintList() function are used in [checkpoint2.cpp](checkpoint2.cpp), but their definitions are missing, please complete these two functions and make sure the program runs and produces the following output.
```console
$ g++ checkpoint2.cpp
$ ./a.out
Linked List of NodeA nodes: 1 -> 2 -> 3 -> 4 -> 5 -> nullptr
Linked List of NodeB nodes: 1 -> 1.41421 -> 1.73205 -> 2 -> 2.23607 -> nullptr
```
**Note**: Hardcoding the PrintList() function to just print the above two messages is strictly prohibited. Also, your functions must be templated functions.
**To complete this checkpoint**, show a TA the implementation and the output of your program.
## Checkpoint 3: Merge Two Lists.
*estimate: 30-40 minutes*
Given two doubly-linked lists: linked list A and linked list B, and both linked lists are sorted. Data in linked list A is sorted in an ascending order. Data in linked list B is also sorted in an ascending order. Merge these two lists such that data in the merged list is still sorted in an ascending order.
More specifically, complete the mergeLists() function in [checkpoint3.cpp](checkpoint3.cpp), such that the program prints the following output.
```console
$ g++ checkpoint3.cpp
$ ./a.out
1 3 5 7 9
2 4 6 8 10
1 2 3 4 5 6 7 8 9 10
10 9 8 7 6 5 4 3 2 1
```
**To complete this checkpoint**, explain to a TA your implementation and show the output of your program.
<!--TODO: how about memory leaks?-->
<!--## Checkpoint 3: Debugging a Merge Sort program.
*estimate: 30-40 minutes*
We expect our program [checkpoint3.cpp](checkpoint3.cpp) to produce the following results when it is compiled and run.
```console
$ g++ checkpoint3.cpp
$ ./a.out
Test Case 1: Original Vector: 5 2 9 1 5 6
Sorted Vector: 1 2 5 5 6 9
Test Case 2: Original Vector: 3 8 2 7 4
Sorted Vector: 2 3 4 7 8
```
But this program currently does not behave as expected. Troubleshoot this program, find the problems and fix them. You can use a debugger.
**To complete this checkpoint**, explain to a TA the bugs you found, show a TA your fixes and run the program to show that your fixes are correct and the program now produces the expected results.-->

View File

@@ -0,0 +1,73 @@
#include <iostream>
#include <cmath>
#include <list>
#include "dslist.h"
int main() {
// =======================================
// CHECKPOINT 1
// create a list of the sqrt of the first 10 integers
dslist<double> a;
for (int i = 0; i < 10; ++i)
a.push_back(sqrt(i));
// print out details of the list
assert (a.size() == 10);
assert (a.front() == 0);
assert (a.back() == 3);
dslist<double>::iterator itr;
std::cout << "Elements = ";
for (itr = a.begin(); itr != a.end(); ++itr)
std::cout << " " << *itr;
std::cout << std::endl;
// clear out the list
a.clear();
/*
assert (a.size() == 0);
*/
/*
// simple tests of push_front, pop_front, and pop_back
a.push_front(5);
a.push_back(7);
a.push_front(3);
a.push_back(9);
assert (a.size() == 4);
assert (*(a.begin()) == 3);
assert (*(++a.begin()) == 5);
assert (*(++(++a.begin())) == 7);
assert (*(++(++(++a.begin()))) == 9);
std::cout << "Elements = ";
for (itr = a.begin(); itr != a.end(); ++itr)
std::cout << " " << *itr;
std::cout << std::endl;
a.pop_back();
a.pop_front();
assert (a.size() == 2);
assert (*(a.begin()) == 5);
assert (*(++a.begin()) == 7);
std::cout << "Elements = ";
for (itr = a.begin(); itr != a.end(); ++itr)
std::cout << " " << *itr;
std::cout << std::endl;
a.pop_back();
a.pop_front();
assert (a.size() == 0);
assert (a.begin() == a.end());
*/
return 0;
}

View File

@@ -0,0 +1,82 @@
/* This program attempts to create two linked list, one linked list consists of nodes of class NodeA type,
* the other linked list consists of nodes of class NodeB type, but we would like to use the same
* PushBack() function to append nodes to the end of these two linked lists, and use the same PrintList() function
* to print the elements in both lists, and thus we need to make the PushBack() and the PrintList() templated functions.
*/
#include <iostream>
#include <cmath>
class NodeA {
public:
int data;
NodeA* next;
NodeA* prev;
};
class NodeB{
public:
double data;
NodeB* next;
NodeB* prev;
};
int main() {
// Part 1: test NodeA class.
// Initialize an empty linked list, consisting of NodeA nodes.
NodeA* headA = nullptr;
// Create nodes and add them to the end of the list using PushBack
for (int i = 1; i <= 5; ++i) {
NodeA* newNode = new NodeA;
// data of NodeA is an int type.
newNode->data = i;
newNode->next = nullptr;
newNode->prev = nullptr;
// Add the node to the end of the list
PushBack(headA, newNode);
}
// Print the linked list to verify the nodes
std::cout << "Linked List of NodeA nodes: ";
PrintList(headA);
// Clean up memory (free nodes)
NodeA* currentA = headA;
while (currentA != nullptr) {
NodeA* next = currentA->next;
delete currentA;
currentA = next;
}
// Part 2: test NodeB class.
// Initialize an empty linked list, consisting of NodeB nodes.
NodeB* headB = nullptr;
// Create nodes and add them to the end of the list using PushBack
for (int i = 1; i <= 5; ++i) {
NodeB* newNode = new NodeB;
// data of NodeA is a double type.
newNode->data = (double)sqrt(i);
newNode->next = nullptr;
newNode->prev = nullptr;
// Add the node to the end of the list
PushBack(headB, newNode);
}
// Print the linked list to verify the nodes
std::cout << "Linked List of NodeB nodes: ";
PrintList(headB);
// Clean up memory (free nodes)
NodeB* currentB = headB;
while (currentB != nullptr) {
NodeB* next = currentB->next;
delete currentB;
currentB = next;
}
return 0;
}

View File

@@ -0,0 +1,94 @@
#include <iostream>
template <class T>
class Node {
public:
T value;
Node<T>* next;
Node<T>* prev;
// constructor
Node(T val) : value(val), next(nullptr), prev(nullptr) {}
};
// function to merge two sorted doubly linked lists
// this function returns a pointer pointing to the head node of the merged list.
template <class T>
Node<T>* mergeLists(Node<T>* head_A, Node<T>* head_B) {
}
int main() {
// create 5 nodes and link them to form a linked list, this is linked list A.
Node<int>* head_A = new Node<int>(1);
Node<int>* second_A = new Node<int>(3);
Node<int>* third_A = new Node<int>(5);
Node<int>* fourth_A = new Node<int>(7);
Node<int>* fifth_A = new Node<int>(9);
// link the nodes
head_A->next = second_A;
second_A->prev = head_A;
second_A->next = third_A;
third_A->prev = second_A;
third_A->next = fourth_A;
fourth_A->prev = third_A;
fourth_A->next = fifth_A;
fifth_A->prev = fourth_A;
// traverse linked list A and print the values
Node<int>* current = head_A;
while (current != nullptr) {
std::cout << current->value << " ";
current = current->next;
}
std::cout << std::endl;
// create 5 nodes and link them to form a linked list, this is linked list B.
Node<int>* head_B = new Node<int>(2);
Node<int>* second_B = new Node<int>(4);
Node<int>* third_B = new Node<int>(6);
Node<int>* fourth_B = new Node<int>(8);
Node<int>* fifth_B = new Node<int>(10);
// link the nodes
head_B->next = second_B;
second_B->prev = head_B;
second_B->next = third_B;
third_B->prev = second_B;
third_B->next = fourth_B;
fourth_B->prev = third_B;
fourth_B->next = fifth_B;
fifth_B->prev = fourth_B;
// traverse linked list B and print the values
current = head_B;
while (current != nullptr) {
std::cout << current->value << " ";
current = current->next;
}
std::cout << std::endl;
Node<int>* head_C;
Node<int>* tail_C;
head_C = mergeLists(head_A, head_B);
// traverse linked list C and print the values
current = head_C;
while (current != nullptr) {
std::cout << current->value << " ";
// keep tracking current and when current reaches nullptr, tail_C will be the tail node.
tail_C = current;
current = current->next;
}
std::cout << std::endl;
// traverse linked list C backwards and print the values
current = tail_C;
while (current != nullptr) {
std::cout << current->value << " ";
current = current->prev;
}
std::cout << std::endl;
return 0;
}

View File

@@ -0,0 +1,268 @@
#ifndef dslist_h_
#define dslist_h_
// A simplified implementation of the STL list container class,
// including the iterator, but not the const_iterators. Three
// separate classes are defined: a Node class, an iterator class, and
// the actual list class. The underlying list is doubly-linked, but
// there is no dummy head node and the list is not circular.
#include <cassert>
// -----------------------------------------------------------------
// NODE CLASS
template <class T>
class Node {
public:
Node() : next_(NULL), prev_(NULL) {}
Node(const T& v) : value_(v), next_(NULL), prev_(NULL) {}
// REPRESENTATION
T value_;
Node<T>* next_;
Node<T>* prev_;
};
// A "forward declaration" of this class is needed
template <class T> class dslist;
// -----------------------------------------------------------------
// LIST ITERATOR
template <class T>
class list_iterator {
public:
// default constructor, copy constructor, assignment operator, & destructor
list_iterator(Node<T>* p=NULL) : ptr_(p) {}
// NOTE: the implicit compiler definitions of the copy constructor,
// assignment operator, and destructor are correct for this class
// dereferencing operator gives access to the value at the pointer
T& operator*() { return ptr_->value_; }
// increment & decrement operators
list_iterator<T>& operator++() { // pre-increment, e.g., ++iter
ptr_ = ptr_->next_;
return *this;
}
list_iterator<T> operator++(int) { // post-increment, e.g., iter++
list_iterator<T> temp(*this);
ptr_ = ptr_->next_;
return temp;
}
list_iterator<T>& operator--() { // pre-decrement, e.g., --iter
ptr_ = ptr_->prev_;
return *this;
}
list_iterator<T> operator--(int) { // post-decrement, e.g., iter--
list_iterator<T> temp(*this);
ptr_ = ptr_->prev_;
return temp;
}
// the dslist class needs access to the private ptr_ member variable
friend class dslist<T>;
// Comparions operators are straightforward
bool operator==(const list_iterator<T>& r) const {
return ptr_ == r.ptr_; }
bool operator!=(const list_iterator<T>& r) const {
return ptr_ != r.ptr_; }
private:
// REPRESENTATION
Node<T>* ptr_; // ptr to node in the list
};
// -----------------------------------------------------------------
// LIST CLASS DECLARATION
// Note that it explicitly maintains the size of the list.
template <class T>
class dslist {
public:
// default constructor, copy constructor, assignment operator, & destructor
dslist() : head_(NULL), tail_(NULL), size_(0) {}
dslist(const dslist<T>& old) { copy_list(old); }
dslist& operator= (const dslist<T>& old);
~dslist() { destroy_list(); }
typedef list_iterator<T> iterator;
// simple accessors & modifiers
unsigned int size() const { return size_; }
bool empty() const { return head_ == NULL; }
void clear() { destroy_list(); }
// read/write access to contents
const T& front() const { return head_->value_; }
T& front() { return head_->value_; }
const T& back() const { return tail_->value_; }
T& back() { return tail_->value_; }
// modify the linked list structure
void push_front(const T& v);
void pop_front();
void push_back(const T& v);
void pop_back();
iterator erase(iterator itr);
iterator insert(iterator itr, const T& v);
iterator begin() { return iterator(head_); }
iterator end() { return iterator(NULL); }
private:
// private helper functions
void copy_list(const dslist<T>& old);
void destroy_list();
//REPRESENTATION
Node<T>* head_;
Node<T>* tail_;
unsigned int size_;
};
// -----------------------------------------------------------------
// LIST CLASS IMPLEMENTATION
template <class T>
dslist<T>& dslist<T>::operator= (const dslist<T>& old) {
// check for self-assignment
if (&old != this) {
destroy_list();
copy_list(old);
}
return *this;
}
template <class T>
void dslist<T>::push_front(const T& v) {
}
template <class T>
void dslist<T>::pop_front() {
}
template <class T>
void dslist<T>::push_back(const T& v) {
Node<T>* newp = new Node<T>(v);
// special case: initially empty list
if (!tail_) {
head_ = tail_ = newp;
} else {
// normal case: at least one node already
newp->prev_ = tail_;
tail_->next_ = newp;
tail_ = newp;
}
++size_;
}
template <class T>
void dslist<T>::pop_back() {
}
// do these lists look the same (length & contents)?
template <class T>
bool operator== (dslist<T>& left, dslist<T>& right) {
if (left.size() != right.size()) return false;
typename dslist<T>::iterator left_itr = left.begin();
typename dslist<T>::iterator right_itr = right.begin();
// walk over both lists, looking for a mismatched value
while (left_itr != left.end()) {
if (*left_itr != *right_itr) return false;
left_itr++; right_itr++;
}
return true;
}
template <class T>
bool operator!= (dslist<T>& left, dslist<T>& right){ return !(left==right); }
template <class T>
typename dslist<T>::iterator dslist<T>::erase(iterator itr) {
assert (size_ > 0);
--size_;
iterator result(itr.ptr_->next_);
// One node left in the list.
if (itr.ptr_ == head_ && head_ == tail_) {
head_ = tail_ = 0;
}
// Removing the head in a list with at least two nodes
else if (itr.ptr_ == head_) {
head_ = head_->next_;
head_->prev_ = 0;
}
// Removing the tail in a list with at least two nodes
else if (itr.ptr_ == tail_) {
tail_ = tail_->prev_;
tail_->next_ = 0;
}
// Normal remove
else {
itr.ptr_->prev_->next_ = itr.ptr_->next_;
itr.ptr_->next_->prev_ = itr.ptr_->prev_;
}
delete itr.ptr_;
return result;
}
template <class T>
typename dslist<T>::iterator dslist<T>::insert(iterator itr, const T& v) {
++size_ ;
Node<T>* p = new Node<T>(v);
p->prev_ = itr.ptr_->prev_;
p->next_ = itr.ptr_;
itr.ptr_->prev_ = p;
if (itr.ptr_ == head_)
head_ = p;
else
p->prev_->next_ = p;
return iterator(p);
}
template <class T>
void dslist<T>::copy_list(const dslist<T>& old) {
size_ = old.size_;
// Handle the special case of an empty list.
if (size_ == 0) {
head_ = tail_ = 0;
return;
}
// Create a new head node.
head_ = new Node<T>(old.head_->value_);
// tail_ will point to the last node created and therefore will move
// down the new list as it is built
tail_ = head_;
// old_p will point to the next node to be copied in the old list
Node<T>* old_p = old.head_->next_;
// copy the remainder of the old list, one node at a time
while (old_p) {
tail_->next_ = new Node<T>(old_p->value_);
tail_->next_->prev_ = tail_;
tail_ = tail_->next_;
old_p = old_p->next_;
}
}
template <class T>
void dslist<T>::destroy_list() {
}
#endif

50
labs/maps/README.md Normal file
View File

@@ -0,0 +1,50 @@
# Lab 9 — Maps
This lab gives you practice initial practice in working with the STL associative container, maps. No downloads
are needed until Checkpoint 3.
## Checkpoint 1
*estimate: 10-20 minutes*
Write a program from scratch that uses a map to find all the modes in an input sequence of integers.
Remember, a mode is an integer that occurs at least as many times in the sequence as any other integer.
Thus, in the sequence
```console
19 83 -12 83 65 19 45 -12 45 19 45
```
the two modes are 19 and 45. Include one command-line argument to provide an input file. Use operator[] for maps when inserting values.
**To complete this checkpoint**: show a TA your debugged implementation and how it runs correctly on several interesting test cases.
## Checkpoint 2
*estimate: 10-20 minutes*
Rewrite your program from checkpoint 1 to use find or insert or both instead of operator[].
**To complete this checkpoint**: show a TA your revised and tested program.
## Checkpoint 3
*estimate: 20-40 minutes*
Please download the [phonebook.cpp](phonebook.cpp) needed for the final checkpoint:
This code implements a simple caller ID program. This program could be used by a university or company
to perform a “reverse lookup” — given the 4 digit phone number extension, return the name of the caller. A
vector of strings stores the complete database of names assigned to phone number extensions. Compile and
run this program. Add your own test cases to the main function.
Part 1: Analyze the computational cost of this program using the big O notation in terms of n the number
of assigned phone numbers in the phonebook, and N the largest possible phone number (number of different
possible phone numbers). What is the running time of constructing the phonebook and of the add and
identify functions? How much memory is used by this program? Express this using order notation. What
would happen if you extended this to a 7- or a 10-digit number? Would it work on a cell phone?
Part 2: Rewrite this program to use maps, storing only the numbers that are assigned. Analyze the cost of
creating the map, and of the add and the identify functions. Test it on 7 digit numbers. In what ways is the
vector version better and in what ways is the map version better?
**To complete this checkpoint**: Present your analysis from Part 1 to a TA, demonstrate the new version
of your program, and be prepared to discuss your analysis of Part 2.

34
labs/maps/phonebook.cpp Normal file
View File

@@ -0,0 +1,34 @@
// A simple "caller ID" program
#include <iostream>
#include <vector>
#include <string>
using namespace std;
// add a number, name pair to the phonebook
void add(vector<string> &phonebook, int number, string const& name) {
phonebook[number] = name;
}
// given a phone number, determine who is calling
void identify(const vector<string> & phonebook, int number) {
if (phonebook[number] == "UNASSIGNED")
cout << "unknown caller!" << endl;
else
cout << phonebook[number] << " is calling!" << endl;
}
int main() {
// create the phonebook; initially all numbers are unassigned
vector<string> phonebook(10000, "UNASSIGNED");
// add several names to the phonebook
add(phonebook, 1111, "fred");
add(phonebook, 2222, "sally");
add(phonebook, 3333, "george");
// test the phonebook
identify(phonebook, 2222);
identify(phonebook, 4444);
}

158
labs/recursion/README.md Normal file
View File

@@ -0,0 +1,158 @@
# Lab 8 — Recursion
This lab gives you practice in the use of recursion to solve problems. All three checkpoints addressed in
this lab deal with finding and counting the number of paths between points on a rectilinear grid. A starting
point (x, y) with non-negative integer coordinates is provided. You are only allowed to move horizontally
and vertically along the grid. Hence, from (x, y) you may move to (x + 1, y), (x 1, y), (x, y 1), (x, y + 1).
However, **keep in mind that your goal is to return to the origin (0, 0) in such a way that you never increase the distance to the origin.** The distance is counted as the minimum number of total vertical and horizontal steps to reach the origin. In
the first checkpoint the grid will be “free”. In the second and third checkpoints, some of the grid locations
will be “blocked” in the sense that you can not move to that point.
Stop now and play with small examples. Draw pictures of a grid. Think about the implications of the rules
before you proceed with the checkpoints.
## Checkpoint 1
*estimate: 10-30 minutes*
Did you notice that the rules prevent certain moves from occurring? What are they? If you dont get them
right you will not be able to do the lab correctly. Confirm your understanding with one of the lab TAs.
Now, write a simple recursive program (from scratch) that reads a starting location, (x, y) and returns the
total number of different paths back to the origin when the entire grid is “free”. Two paths are different if
there is at least one step on the path that is different even if most of the steps are the same. As a hint, when
x == 0 and y == 0 there is 1 path, when x == 2 and y == 1 there are 3 paths, and when x == 2 and
y == 2 there are 6 paths. When youre confident your program is debugged try even bigger values. For what
values does the program take a few seconds to complete on your computer? If you increase these values by
1, how does it impact the running time? Is this what you expected from Big O Notation?
**To complete this checkpoint** show a TA your program to count all paths back to the origin. Be prepared
to discuss the running time of your program for different input values.
## Checkpoint 2
*estimate: 20-40 minutes*
Please download the files:
[start.cpp](start.cpp)
[grid1.txt](grid1.txt)
[grid2.txt](grid2.txt)
[grid3.txt](grid3.txt)
[grid4.txt](grid4.txt)
Starting from the supplied program, start.cpp, write a program to count the number of paths when some
of the grid locations are blocked. When a location is blocked, no path can go through it. Before writing
your own code, compile and run start.cpp. Use the files grid1.txt, etc. as input. These have a sequence
of blocked locations, ending with the point location (0, 0) (which isnt blocked, but signals the end of the
input). The next location is the location for the initial (x, y). By changing this location you will be able to
test your program in different ways.
**To complete this checkpoint** show a TA your code to find and count legal (unblocked) paths.
## Checkpoint 3
*estimate: 30-40 minutes*
Modify your program from Checkpoint 2 so that it finds and outputs a legal path from the given (x, y)
location to the origin. You should both print the sequence of coordinates of a solution path AND modify the
print_grid function to draw the grid a second time with the path marked on the grid with $ symbols. If
there is more than one path, it does not matter which it outputs. Did you notice that all legal paths to the
origin are the same length under the rules provided?
Now lets explore recursion with gdb/lldb. <!--Review the handout from Lab 3, Checkpoint 3:
http://www.cs.rpi.edu/academics/courses/spring23/csci1200/labs/03_debugging/lab_post.pdf-->
Heres a table of equivalent commands in gdb (Linux, WSL) vs. lldb (Mac, Linux, WSL):
https://lldb.llvm.org/lldb-gdb.html
1. Compile your program with the -g flag, so it includes debug info, including line numbers:
```console
clang++ -Wall -g start.cpp -o start.out
```
2. Start gdb (or lldb):
```console
gdb ./start.out
```
3. Set a breakpoint at the first line of the main function:
```console
b main
```
4. Now run/launch the program. Note: Heres where we specify the necessary command line arguments.
```console
r grid2.txt
```
5. Place a breakpoint on the line in main where you first call your recursive function. Replace <NUM> with
the line number in your source code.
```console
b <NUM>
```
gdb will confirm with a message like this:
Breakpoint 2 at 0xwhatever: file start.cpp, line <NUM>
You can review your currently set breakpoints:
```console
info b
```
If you happen to mistype the line number, you can delete the breakpoint and try again. <BNUM> is the
breakpoint number with the incorrect line number.
```console
delete <BNUM>
```
6. Place another breakpoint at the first line of your recursive function. And then let the program run
until it reaches the next breakpoint by typing:
continue
When gdb pauses, it should inform you of the current program line number.
7. Now lets step into your recursive function to get a closer look at recursion. Let the program run until
it enters the first recursive function call:
```console
continue
```
Inspect the data in the function arguments and the grid of booleans. Print the coordinates of the
current location, which are passed in as function arguments named x and y (for example).
```console
print x
print y
```
If you are using grid2.txt as your grid, these values should read x = 9 and y = 7.
8. Lets navigate within our recursive algorithm using step and next. NOTE: Though similar sounding,
these are two different commands in gdb/lldb. step will enter into the details of a called function and
allow you to walk through its code, while next will fully execute one line from the current function
(skipping over all of the details of the function as its executed).
Use the next command to move down until we hit our next recursive call. Once on this line, lets step
into the function call. After you step into the function, you may want to type continue so we can skip
to the next breakpoint. Print out the coordinates of the current location, which should be different.
Also, lets print the boolean values from locations in the grid adjacent to the current position. For
example:
```console
print blocked_grid
print blocked_grid[x][y]
print blocked_grid[x-1][y+1]
```
Because gdb supports using basic math on these variables, we can also print the grid values above,
below, to the left, and to the right of our current position.
9. Use step and continue to go further and further into the recursion. Use backtrace to show the
function calls currently on the call stack, including the specific line numbers. As you walk step by step
through your algorithm and print data, do the variable values and sequence of locations and function
calls match your expectations? Ask a TA or mentor if you have questions about this information.
10. Finally, delete all of the breakpoints and then let the program finish without interruption.
```console
continue
```
**To complete this checkpoint and the entire lab**, present your modified program to a TA or mentor.
Demonstrate your skills with gdb/lldb to print out values of adjacent positions in the blocked grids vector,
to use backtrace, and navigate within recursive function calls using next, step, continue. Be prepared to
discuss how you will use gdb/lldb to help debug your future Data Structures homeworks.

7
labs/recursion/grid1.txt Normal file
View File

@@ -0,0 +1,7 @@
1 0
2 1 2 3
3 3 3 4
5 5
0 0
4 4

11
labs/recursion/grid2.txt Normal file
View File

@@ -0,0 +1,11 @@
2 0 3 1
0 1 3 3
1 2 4 1 5 2
1 3 5 3
0 5 2 5
5 4 6 4 7 4
3 5 4 6 5 6 7 5
7 7 8 8
0 0
9 7

28
labs/recursion/grid3.txt Normal file
View File

@@ -0,0 +1,28 @@
0 1
2 2
2 3
3 3
4 3
3 1
1 4
5 5
6 6
1 9
1 8
1 7
3 7
2 7
3 10
7 7
7 8
7 6
7 5
7 4
7 3
8 3
9 3
10 3
0 0
10 10

41
labs/recursion/grid4.txt Normal file
View File

@@ -0,0 +1,41 @@
0 1
2 2
2 3
3 3
4 3
3 1
1 4
5 5
6 6
1 9
1 8
1 7
3 7
2 7
3 10
7 7
7 8
7 6
7 5
7 4
7 3
8 3
9 3
10 3
15 15
17 15
16 15
18 15
14 15
13 15
12 14
17 18
7 18
7 17
7 16
7 15
7 14
0 0
18 18

130
labs/recursion/start.cpp Normal file
View File

@@ -0,0 +1,130 @@
// Starting code for Checkpoints 2 and 3. This includes
// functions to read the grid and to output it.
#include <fstream>
#include <iostream>
#include <list>
#include <vector>
// A simple class to represent a point location. It only has a
// constructor and a two public member variables. This is one of the
// few times that you are allowed to use non-private member variables.
class Point {
public:
Point(int x0, int y0) : x(x0), y(y0) {}
int x,y;
};
// NOTE: We could use a boolean (true/false) to represent whether each
// cell of the grid was blocked or open. This would be the minimal
// representation for memory.
// However, debuggers (both traditional and memory debuggers) might
// not be able to help debug errors with vectors of booleans, if they
// are efficiently packed by a clever STL implementation. So instead
// we use an enum, to improve readability, and to ensure that the
// status of each grid cell is stored as an integer avoiding debugger
// confusion during development.
enum GRID_STATUS { GRID_CLEAR, GRID_BLOCKED };
// Input the grid and the start location. The input is a sequence of
// x y locations, terminated by x==0 and y==0. The last input, which
// follows 0 0 input, is the start location.
//
// The grid is represented as a 2d vector of GRID_STATUS, with each location
// that is blocked --- meaning that no path can go through --- being
// represented by the value GRID_BLOCKED. The grid is large enough to
// include all blocked points and include the starting location. The
// first coordinate of the vector of vectors is the x coordinate, and
// the second is the y coordinate. The format of the input is
// specified in the lab handout.
void read_grid(std::istream& istr,
std::vector<std::vector<GRID_STATUS> >& blocked_grid,
int& start_x, int& start_y) {
// Read the x y locations into a list of Points. Keep track of the
// max x and max y values so that the size of the grid can be
// determined.
int x, y;
int max_x = 0, max_y = 0; // keep track of the max coordinate values
std::list<Point> blocked_points;
while ((istr >> x >> y) && ! (x==0 && y==0)) {
blocked_points.push_back(Point(x,y));
if (x > max_x) max_x = x;
if (y > max_y) max_y = y;
}
// Now that a 0 0 location has been read, read the start location.
// If this is beyond the max x or y value then update these values.
istr >> start_x >> start_y;
if (start_x > max_x) max_x = start_x;
if (start_y > max_y) max_y = start_y;
// Make a vector of vectors with all entries marked clear.
std::vector<GRID_STATUS> one_row_of_ys(max_y+1, GRID_CLEAR);
std::vector<std::vector<GRID_STATUS> > empty_grid(max_x+1, one_row_of_ys);
blocked_grid = empty_grid;
// For Point in the list, mark the location in the list as blocked.
std::list<Point>::iterator p;
for (p = blocked_points.begin(); p != blocked_points.end(); ++p) {
blocked_grid[p->x][p->y] = GRID_BLOCKED;
}
}
// Output the grid to cout. The form of the output is explained in
// the cout statement below.
void print_grid(const std::vector<std::vector<GRID_STATUS> > & blocked_grid,
unsigned int start_x, unsigned int start_y) {
std::cout << "Here is the grid with the origin in the upper left corner, x increasing \n"
<< "horizontally and y increasing down the screen. An 'X' represents a blocked\n"
<< "location and the 'S' represents the starting location.\n\n";
for (unsigned int y=0; y<blocked_grid[0].size(); ++y) {
for (unsigned int x=0; x<blocked_grid.size(); ++x) {
if (x == start_x && y == start_y)
std::cout << " S";
else if (blocked_grid[x][y] == GRID_BLOCKED)
std::cout << " X";
else
std::cout << " .";
}
std::cout << std::endl;
}
}
int main(int argc, char* argv[]) {
if (argc != 2) {
std::cerr << "Usage: " << argv[0] << " grid-file" << std::endl;;
return 1;
}
std::ifstream istr(argv[1]);
if (!istr) {
std::cerr << "Could not open " << argv[1] << std::endl;
return 1;
}
std::vector<std::vector<GRID_STATUS> > blocked_grid;
int start_x, start_y;
read_grid(istr, blocked_grid, start_x, start_y);
print_grid(blocked_grid, start_x, start_y);
// Start here with your code...
return 0;
}

View File

@@ -0,0 +1,75 @@
# Lab 11 — Stacks and Queues
In this lab, you will find out the answer to this question: When the STL queue library/class is not available to you, can you make your own queue? More specifically, in this lab, you will implement queues in different ways, and then fix memory leaks in the provided program. Start by downloading the provided program [levelOrder.cpp](levelOrder.cpp). The provided program [levelOrder.cpp](levelOrder.cpp) traverses a binary tree by level order. It prints the following message to STDOUT:
```console
Level Order Traversal: 1 2 3 4 5 6 7
Level Order Traversal: 1 2 3 4 5 6 7 8 9
Level Order Traversal: 1 2 3 4 5 6 7 8
```
## Checkpoint 1:
*estimate: 30-40 minutes*
First, read the code of the provided program, and run the program to see its output.
- Play this [animation](https://jidongxiao.github.io/CSCI1200-DataStructures/animations/trees/level_order/index.html) to see how level order traverse works.
The provided program includes the STL queue library with this line:
```cpp
#include <queue>
```
Now, let us assume that the STL queue library is not available, but the STL stack library is avaiable, meaning that you are now not allowed to have this above line in the program, but you can have the following line in the program:
```cpp
#include <stack>
```
Do not change the *main* function. Do not change the *levelOrderTraversal* function, except this line:
```cpp
std::queue<TreeNode*> myQueue;
```
Can you still make the program work? i.e., still traversing a binary tree by level order, when the STL queue library is not available, but the STL stack library is available to you. You can implement your own classes or functions.
**To complete this checkpoint**: Show a TA your program, and your test results. Your program should still produce the same results as the original program. And you must be able to explain your program.
## Checkpoint 2:
*estimate: 30-40 minutes*
Now, let us assume that the STL queue library is not available, and the STL stack library is not avaiable, meaning that you are now not allowed to have either of following two lines in the program:
```cpp
#include <queue>
#include <stack>
```
However, you can include the STL list library like this:
```cpp
#include <list>
```
Still, do not change the *main* function, and do not change the *levelOrderTraversal* function, except this line:
```cpp
std::queue<TreeNode*> myQueue;
```
Can you still make the program work? i.e., still traversing a binary tree by level order, when neither the STL queue library nor the STL stack library is available, but the STL list library is available to you. You can implement your own classes or functions.
**To complete this checkpoint**: Show a TA your program, and your test results. Your program should still produce the same results as the original program. And you must
be able to explain your program.
## Checkpoint 3:
*estimate: 15-20 minutes*
The provided program clearly has memory leaks. Fix the memory leaks.
**To complete this checkpoint**: Show a TA your program, and your test results with either Valgrind or DrMemory.

View File

@@ -0,0 +1,110 @@
#include <iostream>
#include <vector>
#include <queue>
// Definition for a binary tree node.
class TreeNode {
public:
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};
std::vector<std::vector<int>> levelOrderTraversal(TreeNode* root) {
std::vector<std::vector<int>> result;
if (root == nullptr) {
return result;
}
std::queue<TreeNode*> myQueue;
myQueue.push(root);
while (!myQueue.empty()) {
int size = myQueue.size();
std::vector<int> current_level;
for (int i = 0; i < size; i++) {
TreeNode* current = myQueue.front();
myQueue.pop();
std::cout << current->val << " ";
if (current != nullptr) {
current_level.push_back(current->val);
if (current->left != nullptr) {
myQueue.push(current->left);
}
if (current->right != nullptr) {
myQueue.push(current->right);
}
}
}
result.push_back(current_level);
}
return result;
}
int main() {
/* test case 1
* 1
* 2 3
* 4 5 6 7
*/
TreeNode* root1 = new TreeNode(1);
root1->left = new TreeNode(2);
root1->right = new TreeNode(3);
root1->left->left = new TreeNode(4);
root1->left->right = new TreeNode(5);
root1->right->left = new TreeNode(6);
root1->right->right = new TreeNode(7);
std::cout << "Level Order Traversal: ";
levelOrderTraversal(root1);
std::cout << std::endl;
/* test case 2
* 1
* 2 3
* 4 5 6 7
* 8 9
*/
TreeNode* root2 = new TreeNode(1);
root2->left = new TreeNode(2);
root2->right = new TreeNode(3);
root2->left->left = new TreeNode(4);
root2->left->right = new TreeNode(5);
root2->right->left = new TreeNode(6);
root2->right->right = new TreeNode(7);
root2->right->right->left = new TreeNode(8);
root2->right->right->right = new TreeNode(9);
std::cout << "Level Order Traversal: ";
levelOrderTraversal(root2);
std::cout << std::endl;
/* test case 3
* 1
* 2 3
* 4 5 6 7
* 8
*/
TreeNode* root3 = new TreeNode(1);
root3->left = new TreeNode(2);
root3->right = new TreeNode(3);
root3->left->left = new TreeNode(4);
root3->left->right = new TreeNode(5);
root3->right->left = new TreeNode(6);
root3->right->right = new TreeNode(7);
root3->left->left->left = new TreeNode(8);
std::cout << "Level Order Traversal: ";
levelOrderTraversal(root3);
std::cout << std::endl;
return 0;
}

57
labs/trees_I/README.md Normal file
View File

@@ -0,0 +1,57 @@
# Lab 10 — Trees, Binary Trees, & Binary Search Trees
## Checkpoint 1
*estimate: 20-30 minutes*
problem 1: Draw a binary tree with 4 levels with the integers 1-7 such that the sum of elements on every level of the tree is the same.
problem 2: Create a exactly balanced binary search tree with 7 color words (order the colors alphabetically).
problem 3: Draw a exactly-balanced binary search tree containing the letters of the word: uncopyrightable
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
What is the pre-order traversal of the tree above?
problem 4: Now draw a exactly-balanced binary tree of characters such that a post-order traversal spells the word: uncopyrightable
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
&nbsp;
What is the breadth-first traversal of the tree above?
**To complete this checkpoint**: When you have finished all of the problems, discuss your answers with your lab TA or mentor.
## Checkpoint 2
*estimate: 20-35 minutes*
Now lets explore the implementation of the ds_set class, along with the use of recursive functions to manipulate binary search trees. Download and examine the files: [ds_set.h](ds_set.h) and [test_ds_set.cpp](test_ds_set.cpp).
The implementation of *find* provided in ds_set.h is recursive. Re-implement and test a non-recursive replacement for this function.
**To complete this checkpoint**: Show one of the TAs your new code. Be prepared to discuss the running time for the two different versions of *find* for various inputs.
## Checkpoint 3
*estimate: 20-35 minutes*
The implementation of the copy constructor and the assignment operator is not yet complete
because each depends on a private member function called *copy_tree*, the body of which has not yet been
written. Write *copy_tree* and then test to see if it works by “uncommenting” the appropriate code from the
main function.
**To complete this checkpoint**: Test your code and show one of the TAs your new code.

155
labs/trees_I/ds_set.h Normal file
View File

@@ -0,0 +1,155 @@
// Partial implementation of binary-tree based set class similar to std::set.
// The iterator increment & decrement operations have been omitted.
#ifndef ds_set_h_
#define ds_set_h_
#include <iostream>
#include <utility>
// -------------------------------------------------------------------
// TREE NODE CLASS
template <class T>
class TreeNode {
public:
TreeNode() : left(NULL), right(NULL) {}
TreeNode(const T& init) : value(init), left(NULL), right(NULL) {}
T value;
TreeNode* left;
TreeNode* right;
};
template <class T> class ds_set;
// -------------------------------------------------------------------
// TREE NODE ITERATOR CLASS
template <class T>
class tree_iterator {
public:
tree_iterator() : ptr_(NULL) {}
tree_iterator(TreeNode<T>* p) : ptr_(p) {}
tree_iterator(const tree_iterator& old) : ptr_(old.ptr_) {}
~tree_iterator() {}
tree_iterator& operator=(const tree_iterator& old) { ptr_ = old.ptr_; return *this; }
// operator* gives constant access to the value at the pointer
const T& operator*() const { return ptr_->value; }
// comparions operators are straightforward
bool operator== (const tree_iterator& rgt) { return ptr_ == rgt.ptr_; }
bool operator!= (const tree_iterator& rgt) { return ptr_ != rgt.ptr_; }
// increment & decrement will be discussed in Lecture 19 and Lab 11
private:
// representation
TreeNode<T>* ptr_;
};
// -------------------------------------------------------------------
// DS SET CLASS
template <class T>
class ds_set {
public:
ds_set() : root_(NULL), size_(0) {}
ds_set(const ds_set<T>& old) : size_(old.size_) {
root_ = this->copy_tree(old.root_); }
~ds_set() { this->destroy_tree(root_); root_ = NULL; }
ds_set& operator=(const ds_set<T>& old) {
if (&old != this) {
this->destroy_tree(root_);
root_ = this->copy_tree(old.root_);
size_ = old.size_;
}
return *this;
}
typedef tree_iterator<T> iterator;
int size() const { return size_; }
bool operator==(const ds_set<T>& old) const { return (old.root_ == this->root_); }
// FIND, INSERT & ERASE
iterator find(const T& key_value) { return find(key_value, root_); }
std::pair< iterator, bool > insert(T const& key_value) { return insert(key_value, root_); }
int erase(T const& key_value) { return erase(key_value, root_); }
// OUTPUT & PRINTING
friend std::ostream& operator<< (std::ostream& ostr, const ds_set<T>& s) {
s.print_in_order(ostr, s.root_);
return ostr;
}
void print_as_sideways_tree(std::ostream& ostr) const {
print_as_sideways_tree(ostr, root_, 0); }
// ITERATORS
iterator begin() const {
if (!root_) return iterator(NULL);
TreeNode<T>* p = root_;
while (p->left) p = p->left;
return iterator(p);
}
iterator end() const { return iterator(NULL); }
private:
// REPRESENTATION
TreeNode<T>* root_;
int size_;
// PRIVATE HELPER FUNCTIONS
TreeNode<T>* copy_tree(TreeNode<T>* old_root) {
// Implemented in Lab 10
}
void destroy_tree(TreeNode<T>* p) { /* Implemented in Lecture 18 */ }
iterator find(const T& key_value, TreeNode<T>* p) {
if (!p) return iterator(NULL);
if (p->value > key_value)
return find(key_value, p->left);
else if (p->value < key_value)
return find(key_value, p->right);
else
return iterator(p);
}
std::pair<iterator,bool> insert(const T& key_value, TreeNode<T>*& p) {
if (!p) {
p = new TreeNode<T>(key_value);
this->size_++;
return std::pair<iterator,bool>(iterator(p), true);
}
else if (key_value < p->value)
return insert(key_value, p->left);
else if (key_value > p->value)
return insert(key_value, p->right);
else
return std::pair<iterator,bool>(iterator(p), false);
}
int erase(T const& key_value, TreeNode<T>* &p) { /* Implemented in Lecture 19 or 20 */ }
void print_in_order(std::ostream& ostr, const TreeNode<T>* p) const {
if (p) {
print_in_order(ostr, p->left);
ostr << p->value << "\n";
print_in_order(ostr, p->right);
}
}
void print_as_sideways_tree(std::ostream& ostr, const TreeNode<T>* p, int depth) const {
if (p) {
print_as_sideways_tree(ostr, p->right, depth+1);
for (int i=0; i<depth; ++i) ostr << " ";
ostr << p->value << "\n";
print_as_sideways_tree(ostr, p->left, depth+1);
}
}
};
#endif

View File

@@ -0,0 +1,97 @@
#include <iostream>
#include <string>
#include <utility>
#include <cassert>
#include "ds_set.h"
int main() {
// build a set
ds_set<std::string> set1;
std::pair< ds_set<std::string>::iterator, bool > insert_result;
std::string to_insert = std::string("hello");
insert_result = set1.insert(to_insert);
assert(insert_result.second);
insert_result = set1.insert(std::string("good-bye"));
assert(insert_result.second);
insert_result = set1.insert(std::string("friend"));
assert(insert_result.second);
insert_result = set1.insert(std::string("abc"));
assert(insert_result.second);
insert_result = set1.insert(std::string("puppy"));
assert(insert_result.second);
insert_result = set1.insert(std::string("zebra"));
assert(insert_result.second);
insert_result = set1.insert(std::string("daddy"));
assert(insert_result.second);
insert_result = set1.insert(std::string("puppy"));
assert(!insert_result.second && * insert_result.first == std::string("puppy"));
ds_set<std::string>::iterator p = set1.begin();
assert(p != set1.end() && *p == std::string("abc"));
// visualize the set
std::cout << "The set size is " << set1.size()
<< "\nHere are the elements: \n" << set1 << std::endl;
p = set1.find("foo");
if (p == set1.end())
std::cout << "\"foo\" is not in the set\n";
else
std::cout << "\"foo\" is in the set\n"
<< "The iterator points to " << *p << std::endl;
p = set1.find("puppy");
if (p == set1.end())
std::cout << "\"puppy\" is not in the set\n";
else
std::cout << "\"puppy\" is in the set\n"
<< "The iterator points to " << *p << std::endl;
p = set1.find("daddy");
if (p == set1.end())
std::cout << "\"daddy\" is not in the set\n";
else
std::cout << "\"daddy\" is in the set\n"
<< "The iterator points to " << *p << std::endl;
std::cout << "\nHere is the tree, printed sideways.\n"
<< "The indentation is proportional to the depth of the node\n"
<< "so that the value stored at the root is the only value printed\n"
<< "without indentation. Also, for each node, the right subtree\n"
<< "can be found above where the node is printed and indented\n"
<< "relative to it\n\n";
set1.print_as_sideways_tree(std::cout);
/*
// Needed for checkpoint 3
ds_set<std::string> set2(set1);
std::cout << "set1.size() = " << set1.size() << ", set2.size() = " << set2.size() << std::endl;
std::cout << "\nHere is set2 printed sideways:\n";
set2.print_as_sideways_tree(std::cout);
// Now add more stuff to set2.
insert_result = set2.insert(std::string("a"));
assert(insert_result.second);
insert_result = set2.insert(std::string("b"));
assert(insert_result.second);
std::cout << "\nAfter two inserts:\n"
<< "set1.size() = " << set1.size() << ", set2.size() = " << set2.size() << "\n"
<< "\nThe contents of set1:\n" << set1 << std::endl
<< "\nThe contents of set2:\n" << set2 << std::endl;
*/
return 0;
}