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

@@ -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