One of major drawbacks to linked lists is that the access time to elements is linear. Hashtables, on the other hand, have access in constant time. However, linked lists have constant insertion and deletion time given an adjacent node in the list. I am trying to construct a FIFO datastructure with constant access time, and constant insertion/deletion time. I came up with the following code:
unordered_map<string key, T*> hashTable;
list<T> linkedList;
T Foo;
linkedList.push_front(Foo);
hashTable.insert(pair<string, T*>("A", &Foo);
T Bar;
linkedList.push_front(Bar);
hashTable.insert(pair<string, T*>("B", &Bar);
However, this code feels like it is really dangerous. The idea was that I could use the hashtable to access any given element in constant time, and since insertion always occurs at the start of the list, and deletion from the end of the list, I can insert and delete elements in constant time. Is there anything inherently poor about the above code? If I wanted to instead store pointers to the nodes in the linkedList to have constant insertion/deletion time from any node would I just store list< T >::iterator*?
If you build a lookup table you will end up with 2 data structures holding the same data which does not make any sense.
The best thing you can do is making your linked list ordered and build a sparse lookup table in some way to select the start node to search in order to amortize some run time.
It is unclear how you want to access the list elements. From the code you provided, I assume you want the first pushed node as "A", the second pushed node as "B", etc. Then my question is what happens when we delete the first pushed node? Does the second pushed node become "A"?
If the node identifiers don't change when updating the list, your approach seems alright.
If the node identifiers has to change on list update, then here is a lightweight and limited approach:
const int MAX_ELEMENTS = 3;
vector<int> arr(MAX_ELEMENTS + 1);
int head = 0, tail = 0;
int get_size() {
if (head > tail)
return tail + ((int)arr.size() - head);
return tail - head;
}
void push(int value) {
if (get_size() == MAX_ELEMENTS) {
// TODO: handle push to full queue
cout << "FULL QUEUE\n";
return;
}
arr[tail++] = value;
if (tail == (int)arr.size()) {
tail = 0;
}
}
int pop() {
if (get_size() == 0) {
// TODO: handle access to empty queue
cout << "EMPTY QUEUE\n";
return -1;
}
int result = arr[head++];
if (head == (int)arr.size()) {
head = 0;
}
return result;
}
int get_item_at(int id) {
if (id >= get_size()) {
// TODO: index out of range
cout << "INDEX OUT OF RANGE\n";
return -1;
}
int actual_id = head + id;
if (actual_id >= (int)arr.size()) {
actual_id -= (int)arr.size();
}
return arr[actual_id];
}
The above approach will keep indices up-to-date (eg. get_item_at(0) will always return the first node in the queue). You can map ids to any suitable id you want like "A" -> 0, "B" -> 1, etc. The limitation of this solution is that you won't be able to store more than MAX_ELEMENTS in the queue.
If I wanted to instead store pointers to the nodes in the linkedList to have constant insertion/deletion time from any node would I just store list< T >::iterator*?
If identifiers must change with insertion/deletion, then it is going to take O(n) time anyways.
This idea makes sense, especially for large lists where order of insertion matters but things might be removed in the middle. What you want to do is create your own data structure:
template<typename T> class constantAccessLinkedList {
public:
void insert(const T& val) {
mData.push_back(val); //insert into rear of list, O(1).
mLookupTable.insert({val, mData.back()}); //insert iterator into map. O(1).
}
std::list<T>::iterator find(const T& val) {
return mLookupTable[val]; //O(1) lookup time for list member.
}
void delete(const T& val) {
auto iter = mLookupTable.find(val); //O(1)get list iterator
mLookupTable.erase(val); //O(1)remove from LUT.
mData.erase(iter); //O(1) erase from list.
}
private:
std::list<T> mData;
std::unordered_map<T, std::list<T>::iterator> mLookupTable;
};
The reason you can do this with list and unordered_map is because list::iterators are not invalidated on modifying the underlying container.
This will keep your data in order in the list, but will provide constant access time to iterators, and allow you to remove elements in constant time.
Related
I have the following struct:
struct Node;
typedef unordered_map<char, Node*> Table;
struct Node {
Table table = {{'\0', nullptr}};
bool terminal = false;
};
which I use in the class to store the items. So I am trying to write the destructor. My idea was recursively iterate through all nodes until we reach a node with an empty table and then clear the memory for the structure, and then remove the element from the table. But the problem is that it can't compare begin and end iterators.
void clear_memory(Node * cur_node) {
if (cur_node->table.empty()) {
delete cur_node;
return;
}
auto it = cur_node->table.begin();
while (it < cur_node->table.end()) {
clear_memory(it->second);
it = cur_node->table.erase(it);
}
}
~SomeClass() {
clear_memory(head);
}
I was trying to use range-based for loop and it works fine, but I need exactly iterators to erase the elements from table.
P.S. I know that using pointers in that way is a bad idea, but it is study assignment.
You should check for unequality.
while (it != cur_node->table.end()) {
I am trying to merge two doubly linked lists. I created a function already that inserts a new node and in the correct order. The parameters are set by my professor so I can not change them. I am able to add the first item to List1 but am unable to add anymore.
I am getting errors while trying to continue to traverse List2 and add more items to List1. I have tried recursion and a do while loop. While trying to use a do-while loop
struct nodeType{
int info;
nodeType *next;
nodeType *back;
};
class OrderedDoublyLinkedList{
public:
//Insert x in appropriate place in the list to keep it
sorted
void insertNode(int x);
void mergeLists(OrderedDoublyLinkedList &List1,
OrderedDoublyLinkedList &List2);
private:
int count;
nodeType *first;
nodeType *last;
};
void
OrderedDoublyLinkedList::mergeLists(OrderedDoublyLinkedList
&List1, OrderedDoublyLinkedList &List2){
//First Technique
do{
List1.insertNode(List2.first->info);
List2.first->next; //Error: Expresion result unused
}
while(List2.first!=NULL)
//Second Technique
while(List2.first!=NULL)
List1.insertNode(List2.first->info);
mergeLists(&List1, &List2.first->next);
//If I try to use this it says cannot bind to a temporary of
type
I need help accessing the next node to add the rest of the info to List1.
Looks like a simple while loop is all you need
nodeType* n = List2.first;
while (n != NULL)
{
List1.insertNode(n->info);
n = n->next;
}
Although I'm still worried whether this is an acceptable solution. You said that you need to move List2 into List1, and that's not what this code does, This code copies List2 onto List1, List2 is unaffected by this code.
I'm still rather new to C++ and am having trouble implementing an add method that actually works. I've done a hash map in java, but translating it to C++ has proven to be difficult. On top of this, I have to work with constraints (such as not changing anything in the header file), and not being able to use any std library function besides std::string, and std::cout/cin.
Basically, I have to create a hash map that will end up storing usernames (as a key), and passwords (as a value). At this point the username/password combination isn't that important, since implementing a very general class is the point of the exercise.
Just trying to test my HashMap with a default constructor and adding a value ends up giving me a segmentation fault. I'm 100% sure that I'm doing something horribly wrong when trying to implement this hash table. Either I'm not properly connecting the bucket index with a node, or not initializing something correctly.
Here is the header file that I am working with:
#ifndef HASHMAP_HPP
#define HASHMAP_HPP
#include <functional>
#include <string>
class HashMap
{
public:
// Hash functions must conform to these properties:
//
// (1) Given a particular string s repeatedly, they must always
// return the same hash value.
// (2) They do not take the number of buckets into account (as they
// do not receive a parameter that tells them how many buckets
// there are). Any unsigned int value is fair game as a result.
// It will be the job of the HashMap class to reduce the results
// to the range of available bucket indices (e.g., by using the
// % operator).
typedef std::function<unsigned int(const std::string&)> HashFunction;
// This constant specifies the number of buckets that a HashMap will
// have when it is initially constructed.
static constexpr unsigned int initialBucketCount = 10;
public:
// This constructor initializes the HashMap to use whatever default
// hash function you'd like it to use. A little research online will
// yield some good ideas about how to write a good hash function for
// strings; don't just return zero or, say, the length of the string.
HashMap();
// This constructor instead initializes the HashMap to use a particular
// hash function instead of the default. (We'll use this in our unit
// tests to control the scenarios more carefully.)
HashMap(HashFunction hasher);
// The "Big Three" need to be implemented appropriately, so that HashMaps
// can be created, destroyed, copied, and assigned without leaking
// resources, interfering with one another, or causing crashes or
// undefined behavior.
HashMap(const HashMap& hm);
~HashMap();
HashMap& operator=(const HashMap& hm);
// add() takes a key and a value. If the key is not already stored in
// this HashMap, the key/value pair is added; if the key is already
// stored, the function has no effect.
//
// If adding the new key/value pair will cause the load factor of this
// HashMap to exceed 0.8, the following must happen:
//
// (1) The number of buckets should be increased by doubling it and
// adding 1 (i.e., if there were 10 buckets, increase it to
// 2 * 10 + 1 = 21).
// (2) All key/value pairs should be rehashed into their new buckets,
// important because changing the number of buckets will likely
// change which bucket a particular key hashes to (especialy if
// you're using % to determine the index of that bucket).
void add(const std::string& key, const std::string& value);
// remove() takes a key and removes it (and its associated value) from
// this HashMap if it is already present; if not, the function has no
// effect.
void remove(const std::string& key);
// contains() returns true if the given key is in this HashMap, false
// if not.
bool contains(const std::string& key) const;
// value() returns the value associated with the given key in this HashMap
// if the key is stored in this HashMap; if not, the empty string is
// returned. (Going forward, we'll discover that throwing an exception
// is a better way to handle the scenario where the key is not present,
// but we'll conquer that at a later date.)
std::string value(const std::string& key) const;
// size() returns the number of key/value pairs stored in this HashMap.
unsigned int size() const;
// bucketCount() returns the number of buckets currently allocated in
// this HashMap.
unsigned int bucketCount() const;
// loadFactor() returns the proportion of the number of key/value pairs
// to the number of buckets, a measurement of how "full" the HashMap is.
// For example, if there are 20 key/value pairs and 50 buckets, we would
// say that the load factor is 20/50 = 0.4.
double loadFactor() const;
// maxBucketSize() returns the number of key/value pairs stored in this
// HashMap's largest bucket.
unsigned int maxBucketSize() const;
private:
// This structure describes the nodes that make up the linked lists in
// each of this HashMap's buckets.
struct Node
{
std::string key;
std::string value;
Node* next;
};
// Store the hash function (either the default hash function or the one
// passed to the constructor as a parameter) in this member variable.
// When you want to hash a key, call this member variable (i.e., follow
// it with parentheses and a parameter) just like you would any other
// function.
HashFunction hasher;
// You will no doubt need to add at least a few more private members
public:
// our hash function
unsigned int hashFunc(const std::string& key) const;
private:
Node** hashTable;
// We need a variable that will always let us know what the current amount
// of buckets is. bucketCount will use this and return this variable.
unsigned int amountOfBuckets;
// we also need the number of keys currently in the hash map. This is stored here
unsigned int sz;
};
#endif // HASHMAP_HPP
And here is how I am implementing my class (HashMap.cpp):
#include "HashMap.hpp"
// default constructor will initialize Node to default values
// Create a new hash table with the initial bucket count
// Set the amount of buckets to the initial bucket count
// Set the current amount of key/value pairs to zero.
HashMap::HashMap()
: hashTable{new Node*[initialBucketCount]}, amountOfBuckets{initialBucketCount}, sz{0}
{
}
// constructor that initializes HashMap to use a different hash function other
// than the default
HashMap::HashMap(HashFunction hashFunc)
: hasher{hashFunc}, hashTable{new Node*[initialBucketCount]}, amountOfBuckets{initialBucketCount}, sz{0}
{
}
// copy constructor, initializes a new HashMap to be a copy of an existing one
HashMap::HashMap(const HashMap& hm)
// commented out for now :
{
}
// destructor: deallocate the HashMap
HashMap::~HashMap()
{
// delete something here
}
// Assignment operator that overloads equals
HashMap& HashMap::operator=(const HashMap& hm)
{
// FIX COMPILER WARNINGS, DELETE
return *this;
}
// our hash function, this is for our type def HashFunction
// pass this through the constructor
unsigned int HashMap::hashFunc(const std::string& key) const
{
unsigned int hashValue = 0; // what we end up returning
for(int i = 0; i < key.length(); i++) { // iterate through string
int letterIndex = key.at(i) - 96; // create an index for each ASCII char
// first multiply our current hashValue by a prime number
// add to the letter index, to maintain a stable result
// mod by the current amount of buckets on each iteration to prevent overflow
hashValue = (hashValue * 27 + letterIndex) % bucketCount();
}
return hashValue;
}
// add function
void HashMap::add(const std::string& key, const std::string& value)
{
// Check if key being stored matches key already in hashmap
/* BASIC ADD FUNCTION, JUST TO IMPLEMENT UNIT TESTS */
unsigned int hashVal = hashFunc(key);
//Node* prev = nullptr; // keeps track of where we are
Node* current = hashTable[hashVal]; // the place we store a data item into
while(current != nullptr) {
//prev = current; // update previous node to point to current
// this lets us move current without losing our place
current = current->next; // move current to the next node
} // stop once we find an empty node
// current should equal a nullptr
current->key = key; // set key (user)
current->value = value; // set password
current->next = nullptr; // set the next ptr to be null
}
// takes in a key (username), removes it and the value (password) associated
// with it, otherwise, it has no effect
void HashMap::remove(const std::string& key)
{
}
// returns true if given key is in hash map, otherwise returns false
// this acts as a find method
bool HashMap::contains(const std::string& key) const
{
unsigned int hashedValue = hashFunc(key); // hash the key given to get an index
if(hashTable[hashedValue] == nullptr) { // if there are no nodes at given index
return false;
} else { // there are some nodes in the hash table
// iterate through each node in the linked list
// Node* current = hashTable[hashedValue];
// start at first node (this is current)
Node* current = hashTable[hashedValue];
while(current != nullptr && current->key == key) {
current = current->next;
} // end while
if(current == nullptr) { // we reached the end of our linked list
return false; // couldn't find a value
} else { // we found the key provided
return true;
}
} // end if-else
}
// value() returns the value associated with the given key in this HashMap
// if the key is stored in this HashMap; if not, the empty string is returned.
std::string HashMap::value(const std::string& key) const
{
// HANDLES COMPILER WARNINGS, DELETE LATER
return "";
}
// size() returns the number of key/value pairs stored in this HashMap.
unsigned int HashMap::size() const
{
return sz;
}
// bucketCount() returns the number of buckets currently allocated in this HashMap.
// each bucket is an index for the array, we do not include the linked lists.
unsigned int HashMap::bucketCount() const
{
return amountOfBuckets;
}
// loadFactor() returns the proportion of the number of key/value pairs
// to the number of buckets, a measurement of how "full" the HashMap is.
// For example, if there are 20 key/value pairs and 50 buckets, we would
// say that the load factor is 20/50 = 0.4.
double HashMap::loadFactor() const
{
return sz / amountOfBuckets;
}
// maxBucketSize() returns the number of key/value pairs stored in this
// HashMap's largest bucket.
unsigned int HashMap::maxBucketSize() const
{
// HANDLE COMPILER WARNINGS, DELETE LATER
return 0;
}
The main method I am implementing right now is add. Currently I'm just trying to test the class with a main function to even see if I can add values to the map, and test to see if it recognizes whether things are being contained in the map or not. I realize that very little in the class is complete, and that the functions themselves are incomplete, however I'm just trying to test the most basic cases.
Finally, here is my main:
#include <iostream>
#include <string>
#include "HashMap.hpp"
int main()
{
// initialize test
HashMap test1;
std::cout << "TEST 1 HASHMAP OBJECT CREATED" << std::endl;
// add some values
// at this point (11/16/2014), I only have contains, add, and hashFunc
// test these methods below
// constructor doesn't quite work right
std::string key1 = "Alex";
std::string value1 = "password1";
std::string key2 = "Danielle";
std::string value2 = "password2";
std::cout << "strings have been created" << std::endl;
// add to hash map
test1.add(key1, value1);
test1.add(key2, value2);
std::cout << "Keys and values have been added to hash map" << std::endl;
// does key1 contain the word "hi"? no, should return false
std::cout << "Hash map contains word hi?: " << test1.contains("hi") << std::endl;
// does key2 contain word "Danielle"? yes, should return true
std::cout << "Hash map contains word Danielle?: " << test1.contains("Danielle") << std::endl;
return 0;
}
I am using a pre-built script to run the program after I build it. When run, I get this output:
TEST 1 HASHMAP OBJECT CREATED
strings have been created
./run: line 43: 10085 Segmentation fault (core dumped) $SCRIPT_DIR/out/bin/a.out.$WHAT_TO_RUN
Basically the segmentation fault happens during the add function. So what is actually going on with add? And how can I understand what the hash map should be doing better?
Your own comment that current should equal nullptr correctly foretells the failure at the next line:
// current should equal a nullptr
current->key = key; // set key (user)
It's generally bad practice to assume a newly allocated array will be full of nullptr.
You need to set it to all nullptr in the constructor.
There are other problems with your add() function.
Here's a stab at making it at least fit for purpose:
void HashMap::add(const std::string& key, const std::string& value)
{
// Check if key being stored matches key already in hashmap
/* BASIC ADD FUNCTION, JUST TO IMPLEMENT UNIT TESTS */
unsigned int hashVal = hashFunc(key);
Node* head=hashTable[hashVal];
Node* current = head;
if(current==nullptr){
//Nothing in this bucket so it's definitely new.
current=new Node();
current->key = key; // set key (user)
current->value = value; // set password
current->next = nullptr; // set the next ptr to be nullptr.
hashTable[hashVal]=current;
return;
}
do { //It's a do-while because the if statement above has handled current==nullptr.
if(current->key==key){
//We've found a match for the key.
//Common hash-table behavior is to overwrite the value. So let's do that.
current->value=value;
return;
}
current = current->next; // move current to the next node
} while(current != nullptr) // stop once we go past the last node.
//Finally, we found hash collisions but no match on key.
//So we add a new node and chain it to the node(s) already there.
//
//Sometimes it's a good idea to put the new one at the end or if it's likely to get looked up
//it's also a good idea to put it at the start.
//It might be a good idea to keep the collision chain sorted and insert into it accordingly.
//If we sort we can dive out of the loop above when we pass the point the key would be.
//
//However for a little example like this let's put the new node at the head.
current=new Node();
current->key = key; // set key (user)
current->value = value; // set password
current->next = head; // set the next pointer to be the old head.
hashTable[hashVal]=current;
}
PS: There's also a problem with your hash-function.
You're taking modulo the hash-table size inside the main loop.
That will only have the effect of restricting the distribution and giving a dreadful spread.
At least move it to the last line of that function:
return hashValue % bucketCount() ;
However I recommend moving it into the add function:
unsigned int hashVal = hashFunc(key); //Assume modified to not use bucketCount().
unsigned int tableIndex=hashVal % bucketCount();//Reduce hash to a valid index.
Node* head=hashTable[tableIndex]; //TODO: Do same in the other accesses to hashTable...
You could then store the full hash-value in the Node structure and use that as a stronger pre-comparison before current->key==key. If the hash-table is likely to be very full you can get a big performance gain. It depends if you want to spare the bytes for an unsigned int per Node.
If you did that and you took the tip to sort the collision chain, you would do so by hash-code and could normally avoid comparing any strings at add() new key or get() not-there and normally only once in a successful get() or overwriting add().
In your HashMap::add, you're dereferencing a nullpointer. You create an array of node pointers with size of 10 elements in your constructor, but in your code you never create any node objects and assign to the pointers in your array. So when you do:
current->key = key;
you're accessing a node-pointer that is 0.
In my application I have a (unbalanced) tree datastructure. This tree is simply made of "std::list of std::lists" - node holds an arbitrary "list" of sub-nodes. Using this instead of a single list made the rest of the application a lot easier. (The program is about changing moving nodes from one tree to another tree / another part in the tree / to it's own tree).
Now an obvious task is to find a subtree inside a "tree". For non-recursive searches it is simple enough:
subtree_iterator find_subtree(const N& n) {
auto iter(subtrees.begin());
auto e(subtrees.end());
while (iter != e) {
if ((*iter)->name == n) {
return iter;
}
++iter;
}
return e;
}
Which returns an iterator to the subtree position. The problem however starts when I try to implement a multi-level search. Ie, I wish to search for hello.world.test where the dots mark a new level.
Searching worked alright
subtree_iterator find_subtree(const pTree_type& otree, std::string identify) const {
pTree_type tree(otree);
boost::char_separator<char> sep(".");
boost::tokenizer<boost::char_separator<char> > tokens(identify, sep);
auto token_iter(tokens.begin());
auto token_end(tokens.end());
subtree_iterator subtree_iter;
for (auto token_iter(tokens.begin()); token_iter != token_end; ++token_iter) {
std::string subtree_string(*token_iter);
subtree_iter = tree->find_subtree_if(subtree_string);
if (subtree_iter == tree->subtree_end()) {
return otree->subtree_end()
} else {
tree = *subtree_iter;
}
}
return subtree_iter;
}
On first glace it seemed to work "correct", however when I try to use it, it fails. Using it would be like
auto tIn(find_subtree(ProjectTree, "hello.world.test"));
if (tIn != ProjectTree->subtree_end()) {
//rest
}
however that gives a debug assertion error "list iterators not compatible". This isn't too weird: I'm comparing a iterators from different lists to each other. However I could I implement such a thing? My "backup" option would be to return a std::pair<bool,iterator> where the boolean part determines if the tree actually exists. Is there another method, short of making the whole tree single list?
You should not work on iterators internaly. Use nodes instead.
template <typename T>
struct Node {
T item;
Node<T>* next;
};
Then encapsulate your Node in an iterator facade like this :
template<typename T>
class iterator {
private:
Node<T>* node;
public:
...
};
Then use a generic invalid node (when node is nullptr) that is returned whenever end() is reached or returned.
Note that what i suggest is a single linked list (not double linked list as the standard one). this is because you can't go back from an invalid generic end() iterator that point to an invalid null node.
If you don't use iterator operator--() in your algorithms this should be fine.
std::vector<list_iterator> stack to traverse? Where the .back() of the stack is the only one allowed to be equal to end() of the previous one, and .front() is an iterator to the root list?
Let's say I'm using a non-standard linked-list class, List.h. This class is functioning, template'd and has the typical features of add/remove to front and add/remove to back, isEmpty(), etc.
This list does not have any begin() and end() functionality. Also, does a linked-list class have to include iterator functionality? Or is that something I can create on my own when I create a new List?
I'm used to working with STL, so I would usually use this code:
typedef vector<OBJECT>::iterator QuoteIt;
for(QuoteIt i = deposits.begin(); i != deposits.end(); ++i)
Anyway, lets say I create a new "List".
List<int>deposits;
or even a List of Objects
List<OBJECT>deposits;
So let's say I addToBack() 20 different integers, so that creates the appropriate # of new nodes.
Now, how can I traverse this list so I can find a sum of all these ints? Is that possible, or does my current functionality prevent that? I would have to implement some sort of iterator to my List Class?
Now I know I could keep an outside variable, every time I do an addToBack() call to keep track of my sums. However, I want the code to be compatible with Lists of Objects as well. (I want to be able to search one value in a node, and retrieve another value in the same node eventually)
I'm so used to working with stl::list and creating a for loop with iterators, I really dont' know how to get this working with other classes.
btw here is the code for List():
template<class NODETYPE>
class List{
public:
List();
~List();
void insertAtFront(const NODETYPE &);
void insertAtBack(const NODETYPE &);
bool removeFromFront( NODETYPE &);
bool removeFromBack( NODETYPE &);
bool isEmpty() const;
private:
ListNode< NODETYPE > *firstPtr; //pointer to first node
ListNode< NODETYPE > *lastPtr;
//Function to allocate a new node
ListNode< NODETYPE > *getNewNode ( const NODETYPE &);
};
//default constructor
template <class NODETYPE>
List< NODETYPE > ::List()
: firstPtr(0),
lastPtr(0)
{
cout<<"Creating Nodes! \n\n!"<<endl;
}
//deconstructor
template <class NODETYPE>
List<NODETYPE>::~List(){
if(!isEmpty() ){
cout<<"Destroying nodes!"<<endl;
ListNode<NODETYPE> *currentPtr=firstPtr;
ListNode<NODETYPE> *tempPtr;
while( currentPtr !=0){
tempPtr = currentPtr;
currentPtr=currentPtr->nextPtr;
delete tempPtr;
}
}
cout<<"All nodes destroyed! \n\n";
}
template <class NODETYPE>
bool List <NODETYPE>::removeFromFront( NODETYPE & value){
if ( isEmpty() )
return false;
else{
ListNode<NODETYPE> *tempPtr = firstPtr;
if (firstPtr== lastPtr)
firstPtr=lastPtr = 0;
else
firstPtr=firstPtr->nextPtr;
value = tempPtr->data;
delete tempPtr;
return true;
}
}
template <class NODETYPE>
bool List<NODETYPE>::removeFromBack(NODETYPE &value)
{
if (isEmpty())
return false;
else{
ListNode< NODETYPE> *tempPtr = lastPtr;
if( firstPtr == lastPtr)
firstPtr = lastPtr = 0;
else{
ListNode<NODETYPE> *currentPtr=firstPtr;
//Finds second to last element
while(currentPtr->nextPtr !=lastPtr)
currentPtr=currentPtr->nextPtr;
lastPtr = currentPtr;
currentPtr->nextPtr=0;
}
value = tempPtr->data;
delete tempPtr;
return true;
}
}
//Checks to see if list is empty
template< class NODETYPE>
bool List< NODETYPE >::isEmpty() const{
return firstPtr == 0;
}
//returns a pointer to newly created Node
template<class NODETYPE>
ListNode<NODETYPE> *List<NODETYPE>::getNewNode(const NODETYPE &value){
return new ListNode<NODETYPE>(value);
}
In response to:
Now, how can I traverse this list so I can find a sum of all these ints? Is that possible, or does my current functionality prevent that? I would have to implement some sort of iterator to my List Class?
You need to implement a way to iterate over your list that does not (as a side-effect) destroy your list.
In response to:
Now, how can I traverse this list so I
can find a sum of all these ints? Is
that possible, or does my current
functionality prevent that? I would
have to implement some sort of
iterator to my List Class?
No matter how you design a linked list, you must have some sort of pointer to the beginning of the list, and you have to have a way of knowing when you are at the end (e.g. when "next" is null, or by having a pointer to the end). By exposing those data one way or another, you can always set up a list traversal:
Start at the beginning. (In your case, get a hold of firstPtr.)
If you are not at the end, move to the next element. (In your case, get ->nextPtr.)
Using that pattern to accumulate a value as you visit each element you should be able to handle your task with ease.
If your list does not give you public access to its beginning, then it is certainly not a general-purpose list!
You can approach this many ways. You can either choose to create your own iterator or give public access to the list's head.
Option 1 is compatible with stl lists so you might want to go that route. An iterator is essentially a ptr that overrides the inc and dec operators to go to the next or previous position in the list.
If you studied basic data structures at all, you would know that traversing a list is trivial.
Node<type> t = head;
while (t != NULL)
{
// process node here
t = t->next;
}
The code can differ depending on if you use dummy nodes at all.
Since there is no functionality to just get the node without removing it from the list, you can't simply make an "add-on" iterator without changing the List class. The least you would need to do is to either
friend the external ListIterator class
friend free begin and end functions
add begin and end functions to the List class
Without any of those three, you can't achieve what you want.
Your list seems to have 2 ways of iterating it (forwards and backwards)
List<int>deposits;
.. add stuff:
int o;
int sum = 0;
while(deposits.removeFromFront(o)) {
sum+=o;
}
The bad thing though, is that iterating it, you also destroy the list,
you could provide public accessors to List::firstPtr and ListNode::nextPtr in which case you could do:
List<int>deposits;
.. add stuff:
int sum = 0;
for(ListNode<int> *ptr = deposits.firstPtr; ptr ; ptr = ptr->nextPtr)
sum+=ptr->data;
However, use an existing STL container if you can.