问题
I'm having some trouble creating a copy constructor for my hash map class right now. Currently, I understand how to do a copy constructor for arrays, by copying things over from the original array to the next. For example, here is what would be done for an array list:
ArrayList::ArrayList(const ArrayList& a)
: items{new std::string[a.cap]}, sz{a.sz}, cap{a.cap}
{
// arrayCopy is a for loop that does items[i] = a.items[i] on each iteration
arrayCopy(items, a.items, sz);
}
I understand that we need to initialize values to a new array list, and copy them over to a new list of arrays. However, I'm having trouble wrapping my mind around doing this to a separately chained hash map.
In my HashMap.hpp file, I have an unmodifiable structure, like this:
struct Node
{
std::string key;
std::string value;
Node* next;
};
I need help understanding how to put each node into my copy constructor. This is my copy constructor, without the actual "copying" code:
HashMap::HashMap(const HashMap& hm)
: hashTable{new Node*[hm.amountOfBuckets]}, amountOfBuckets{hm.amountOfBuckets}, sz{hm.sz}
{
}
How would I accomplish properly iterating through each hash table index, and creating a new node depending on how many there are in the original table? Would I have to create four Nodes, two to keep track of the new table, and two to keep track of the original table?
I tried to implement this and also tried to implement a way to copy over values within a do while loop. This is my code (that doesn't work and completely sucks :( )
for(unsigned int i = 0; i < amountOfBuckets; i++) {
// target
Node* newHead = hashTable[i];
Node* newCurrent = newHead;
// source
Node* head = hm.hashTable[i];
Node* current = head;
do{
newCurrent = new Node();
newCurrent->key = current->key;
newCurrent->value = current->value;
newCurrent->next = current->next;
newCurrent = hashTable[i];
} while(newCurrent != nullptr);
With this I run into segmentation faults. I'm really not quite sure how to properly copy over each value into the new hash table? Or how I should go about linking it all together?
Here is the declarations for HashMap.hpp
#ifndef HASHMAP_HPP
#define HASHMAP_HPP
#include <functional>
#include <string>
class HashMap
{
public:
typedef std::function<unsigned int(const std::string&)> HashFunction;
static constexpr unsigned int initialBucketCount = 10;
public:
HashMap();
// This constructor instead initializes the HashMap to use a particular
// hash function instead of the default
HashMap(HashFunction hasher);
HashMap(const HashMap& hm);
~HashMap();
HashMap& operator=(const HashMap& hm);
void add(const std::string& key, const std::string& value);
void remove(const std::string& key);
bool contains(const std::string& key) const;
std::string value(const std::string& key) const;
unsigned int size() const;
unsigned int bucketCount() const;
double loadFactor() const;
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;
};
// hash function gets stored in here
HashFunction hasher;
private:
Node** hashTable;
unsigned int amountOfBuckets;
unsigned int sz;
public:
unsigned int getTableIndex(unsigned int hashVal) const;
};
And here is my (incomplete) code for HashMap.cpp. Also, I will not be using the hash function currently in the namespace. I just used it as an easy way to predict bucket indices to test my add/remove functions.
#include <iostream>
#include "HashMap.hpp"
namespace {
unsigned int easyHashFunc(const std::string& key) {
unsigned int hashValue = 0;
for(int i = 0; i < key.length(); i++) {
int letterIndex = key.at(i);
hashValue += letterIndex; // just add up the letters
} // end for
return hashValue;
} // end easyHashFunc
}
HashMap::HashMap()
: hasher{easyHashFunc}, 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 hasher)
: hasher{hasher}, hashTable{new Node*[initialBucketCount]()}, amountOfBuckets{initialBucketCount}, sz{0}
{
}
HashMap::HashMap(const HashMap& hm)
: hashTable{new Node*[hm.amountOfBuckets]}, amountOfBuckets{hm.amountOfBuckets}, sz{hm.sz}
{
for(unsigned int i = 0; i < amountOfBuckets; i++) {
Node* newHead = hashTable[i];
Node* newCurrent = newHead;
// source
Node* head = hm.hashTable[i];
Node* current = head;
do{
newCurrent = new Node();
newCurrent->key = current->key;
newCurrent->value = current->value;
newCurrent->next = current->next;
newCurrent = hashTable[i];
} while(newCurrent != nullptr);
}
}
// destructor: deallocate the HashMap
HashMap::~HashMap()
{
for(unsigned int i = 0; i < amountOfBuckets; i++) {
Node* nextNode = hashTable[i]; // store each hashtable list in a bucket node
while(nextNode != nullptr) {
Node* deleteCurrent = nextNode; // set current to the bucket node (head)
nextNode = nextNode->next; // delete current is on the first node, update head to second node
delete deleteCurrent;
} // end while
} // end for
// once done, delete hash table
delete[] hashTable;
} // end destructor
// Assignment operator that overloads equals
HashMap& HashMap::operator=(const HashMap& hm)
{
// incomplete
return *this;
}
void HashMap::add(const std::string& key, const std::string& value)
{
// Check if key being stored matches key already in hashmap
unsigned int hashedValue = hasher(key); // get hash value (unmodified by buckets)
unsigned int tableIndex = getTableIndex(hashedValue); // get the table index
// case 1, check to see if current is nullptr, meaning our first node
// is the one we should use, ie. we don't need to traverse the list
if(contains(key) == true) { // if key is already in the hashtable
return; // exit program
} else { // otherwise, key is not in the hash table
Node* head = hashTable[tableIndex];
Node* current = head;
if(current == nullptr) {
// nothing in bucket
// create a new node
current = new Node();
current->key = key; // set username
current->value = value; // set pw
current->next = nullptr;
hashTable[tableIndex] = current;
return; // exit
} else {
do {
current = current->next; // advance to next node
}while(current != nullptr);// end while
// currently at node we want to insert key/value at
current = new Node();
current->key = key; // set key(username)
current->value = value; // set value (pw)
current->next = head;
hashTable[tableIndex] = current; // set next to point to nullptr
} // end inner if-else (creates node)
} // end outer if-else
} // end add
// 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)
{
unsigned int hashedValue = hasher(key);
unsigned int tableIndex = getTableIndex(hashedValue);
if(contains(key) == false) { // could not find key in bucket
return; // do nothing
} else {
Node* prevNode = hashTable[tableIndex];
Node* delNode = prevNode;
if(prevNode->key == key) { // first one is a match
hashTable[tableIndex] = prevNode->next; // set the head of the hash table to point to the next node
delete delNode;
return; // exit
} else { // otherwise, we must loop through and find the node we want to delete
do{
// check for match, if found, break out of do while
if(delNode->key == key) {
break;
}
prevNode = delNode; // save current node in previous
delNode = delNode->next; // point the searched node to the next node
}while(delNode != nullptr); // end do while
// set the previous node to point to delNodes next node
prevNode->next = delNode->next;
} // end inner if-else
delete delNode; // de-allocate
} // end outer if-else
} // end remove()
// 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 = hasher(key); // hash the key given to get an index
unsigned int tableIndex = getTableIndex(hashedValue); // get the table index
Node* current = hashTable[tableIndex];
// iterate through each node in the linked list
// start at first node (this is current)
while(current != nullptr) {
if(current->key == key) {
return true; // found match, exit
}
current = current->next;
} // end while
return false; // we haven't found a match
}
// 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
{
if(contains(key) == true) { // found match
unsigned int hashedValue = hasher(key); // hash the key given to get an index
unsigned int tableIndex = getTableIndex(hashedValue); // get the table index
Node* current = hashTable[tableIndex];
while(current != nullptr && current->key != key) {
current = current->next;
}
return current->value; // return value after traversal
} else {
return ""; // no match, return empty string
}
}
unsigned int HashMap::size() const
{
return sz;
}
unsigned int HashMap::bucketCount() const
{
return amountOfBuckets;
}
double HashMap::loadFactor() const
{
return sz / amountOfBuckets;
}
// return the table index for a given hashvalue
unsigned int HashMap::getTableIndex(unsigned int hashVal) const {
return hashVal % amountOfBuckets;
}
回答1:
HashMap::HashMap(const HashMap& hm)
{
amountOfBuckets=hm.amountOfBuckets;
hashTable= new Node* [amountOfBuckets];
for (int i=0; i<amountOfBuckets; i++)
{
Node* n = hm.hashTable[i];
Node** p = &hashTable[i];
*p = NULL;
while (n)
{
Node* c = new Node(*n); // node copy constructor, should set n->next to null
*p = c;
p=&c->next;
n=n->next;
}
}
}
If you don't want Node copy constructor replace Node* c = new Node(*n); with:
Node* c = new Node;
c->key = n->key;
c->value = n->value;
c->next = NULL;
来源:https://stackoverflow.com/questions/27096794/writing-a-valid-copy-constructor-for-a-hash-map-in-c