// ---------------------------------------------------------------------------
// SimpleLinkedList.h
//
// A generic linked list class. (There is already a list class in the
// Standard C++ Library; this class is just an illustration of how such a
// class is written from scratch!)
// ---------------------------------------------------------------------------
#ifndef _SIMPLE_LINKED_LIST_H
#define _SIMPLE_LINKED_LIST_H
#include <stdexcept>
using namespace std;
// A list object is represented as a doubly-linked ring of nodes. One
// header (or "dummy") node always exists, so that insertion and removal
// can be implemented consistently whether or not it is taking place
// at the middle or the ends.
template<class T>
class List {
// A private local class for the nodes that are linked together.
private:
class Node {
public:
Node* previous;
T data;
Node* next;
Node(Node* previous, T data, Node* next):
previous(previous), data(data), next(next) {
}
};
// The list itself is just a pointer to the dummy header node.
private:
Node* header;
int size;
// Constructors and Destructors
public:
// Constructs a new EMPTY list. The empty list is represented as one
// containing only the header.
List(): header(new Node(0, T(), 0)), size(0) {
header->previous = header;
header->next = header;
}
// The copy constructor!
List(const List& other): header(new Node(0, T(), 0)), size(other.size) {
header->previous = header;
header->next = header;
for (Node* n = other.header->next; n != other.header; n = n->next) {
Node* newNode = new Node(header->previous, n->data, header);
newNode->previous->next = newNode;
newNode->next->previous = newNode;
}
}
// Deletes all the nodes. TODO: THIS IS AN INEFFICIENT so it should be
// rewritten.
~List() {
destroy();
delete header;
}
// Behavior
public:
// Returns the number of items in the list.
int getSize() {
return size;
}
// Returns whether the list is empty.
bool isEmpty() {
return size == 0;
}
// Adds an item so that it has position index. The index positions start
// at zero. If the index argument is not in the range 0..size then an
// out_of_range is thrown.
void add(int index, T item) {
addBefore(item, (index == size ? header : nodeAt(index)));
}
// Removes the item at position index. Throws an out_of_range if the
// index is not in the range 0..size-1.
void remove(int index) {
remove(nodeAt(index));
}
// Returns the index of the first occurence of the given item, or -1
// if not found.
int indexOf(T item) {
int index = 0;
for (Node* n = header->next; n != header; n = n->next) {
if (n->data == item) {
return index;
}
index++;
}
return -1;
}
// Retrieve the item at the given index.
T get(int index) {
return nodeAt(index)->data;
}
// Update the item at the given index.
void set(int index, T item) {
nodeAt(index)->data = item;
}
// Assignment Operator
List& operator=(const List& other) {
if (&other == this) {
return *this;
}
destroy();
size = other.size;
for (Node* n = other.header->next; n != other.header; n = n->next) {
Node* newNode = new Node(header->previous, n->data, header);
newNode->previous->next = newNode;
newNode->next->previous = newNode;
}
return *this;
}
// Returns whether this list has the same size, and values in the
// same positions, as another.
bool operator==(const List& other) {
if (size != other.size) {
return false;
}
Node* n1 = header->next;
Node* n2 = other.header->next;
while (n1 != header && n2 != other.header) {
if (n1->data != n2->data) {
return false;
}
n1 = n1->next;
n2 = n2->next;
}
return true;
}
// Helpers
private:
// Deletes all the nodes in this list except the header.
void destroy() {
while (!isEmpty()) {
remove(0);
}
}
// Return a pointer to the node at the given index position.
Node* nodeAt(int index) {
if (index < 0 || index > size) {
throw out_of_range("Invalid list position");
}
Node* n = header;
for (int i = 0; i <= index; i++) {
n = n->next;
}
return n;
}
// Add a new node containing the given value before the node
// pointed to by n.
void addBefore(T item, Node* n) {
Node* newNode = new Node(n->previous, item, n);
newNode->previous->next = newNode;
newNode->next->previous = newNode;
size++;
}
// Remove the node pointed to by n.
void remove(Node* n) {
if (n == 0 || n == header) {
throw runtime_error("The list class is screwed up");
}
n->previous->next = n->next;
n->next->previous = n->previous;
size--;
}
};
#endif
// ---------------------------------------------------------------------------
// testlinkedlist.cpp
//
// A test script for the SimpleLinkedList class.
// ---------------------------------------------------------------------------
#include <iostream>
#include <strstream>
#include <cassert>
#include <cmath>
#include <stdexcept>
using namespace std;
#include "SimpleLinkedList.h"
// This helper allows much more concise assertion expressions.
// Check them out in the body of main() below. NOTE: I'm using
// a strstream (which takes in a char*) rather than a sstream
// (which takes in a string) because when I wrote this my C++
// compiler (gcc 2.95.2) didn't have an sstream class!
List<int> stringToIntList(const char* s) {
List<int> result;
istrstream stream(s);
while (true) {
int i;
stream >> i;
if (!stream) break;
result.add(result.getSize(), i);
}
return result;
}
// Well, this isn't too complete. Add more tests for extra credit.
int main(int argc, char** argv) {
List<int> list;
// Some tests of add, indexOf, isEmpty, getSize and ==
assert(list.isEmpty() && list.getSize() == 0);
list.add(0, 5);
list.add(1, 6);
list.add(2, 7);
list.add(1, 4);
assert(!list.isEmpty() && list.getSize() == 4);
assert(list.indexOf(7) == 3);
assert(list == stringToIntList("5 4 6 7 "));
// Test that copy constructor produces equal lists and
// does not share values.
List<int> otherList(list);
assert(list == otherList);
otherList.set(1, 10);
assert(list == stringToIntList("5 4 6 7 "));
assert(otherList == stringToIntList("5 10 6 7 "));
assert(! (list == otherList));
// Test assignment. Make sure assignment to self doesn't
// crash, and that after assignment the lists are ==.
otherList = otherList;
list = list;
list = otherList;
assert(list == otherList);
assert(list.get(1) == 10);
// Test out_of_range errors are properly thrown.
bool errorHappened = false;
try {
list.add(-4, 3);
}
catch (out_of_range e) {
errorHappened = true;
}
assert(errorHappened);
// ------------------------------------------------------------------
// TODO More tests here. Extra credit for you if you do them for me.
// You have to test EVERYTHING.
// ------------------------------------------------------------------
cout << "All tests have passed\n";
return 0;
}