Writing a valid copy constructor for a hash map in C++

馋奶兔 提交于 2019-12-13 06:56:52

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!