【问题标题】:Multiple Hash Tables for the Word Count Project字数统计项目的多个哈希表
【发布时间】:2019-05-22 06:19:03
【问题描述】:

我已经写了一个工作项目,但我的问题是,它比我最初的目标要慢得多,所以我有一些关于如何改进它的想法,但我不知道如何实现这些想法还是我应该首先实施这些想法?

我项目的主题是,读取一个包含推文的 CSV (Excel) 文件并计算其中的每个单词,然后显示最常用的单词。
(Excel的每一行都有关于推文和推文本身的信息,我应该只关心推文)

我不会分享整个代码,而只是简单地写下我到目前为止所做的事情,并且只询问我正在努力的部分。
首先,我想道歉,因为这将是一个很长的问题。

重要提示:我唯一应该关注的是速度、存储或大小不是问题。

所有步骤:

  • 从 Excel 文件中读取一个新行。
  • 从整行中找到“tweet”部分并将其存储为字符串。
  • 将推文字符串拆分为单词并将其存储在数组中。
  • 对于存储在数组中的每个单词,计算该单词的 ASCII 值。

(为了找到单词的 ascii 值,我只需将它具有的每个字母的 ascii 值求和)

  • 将单词以 ASCII 值的键放入哈希表中。

(例如:单词“hello”的ascii值为104+101+108+108+111 = 532,所以在hast表中与key 532一起存储)

  • 在哈希表中,仅存储单词“作为字符串”和键值“作为 int”,并且单词的计数(使用相同单词的次数)存储在单独的数组中。

我将分享 Insert 函数(用于向 Hashtable 插入一些内容),因为我相信如果我尝试不使用代码来解释这部分内容会令人困惑。

void Insert(int key, string value) //Key (where we want to add), Value (what we want to add)
{

    if (key < 0) key = 0; //If key is somehow less than 0, for not taking any error key become 0.

    if (table[key] != NULL) //If there is already something in hast table 
    {       

        if (table[key]->value == value) //If existing value is same as the value we want to add
        {               
            countArray[key][0]++;
        }
        else //If value is different,
        {           
            Insert(key + 100, value);  //Call this function again with the key 100 more than before.
        }
    }
    else //There is nothing saved in this place so save this value
    {           
        table[key] = new HashEntry(key, value); 
        countArray[key][1] = key;
        countArray[key][0]++;           
    }

}

所以“插入”功能分为三部分。

  • 如果具有给定键的 hast 表为空,则将值添加到哈希表。
  • 如果具有给定键的 hast 表不为空,则意味着我们已经使用此 ascii 值放置了一个单词。

因为不同的词可以有完全相同的 ascii 值

  • 程序首先检查这是否是同一个词。
  • 如果是,则计数增加(在计数数组中)。
  • 如果没有,则再次调用 Insert 函数,键值为 (same key value + 100),直到找到空白空间或相同的值。

读取整行并将每个单词存储在哈希表中 ->

对 Count 数组进行排序
打印前 10 个元素


程序到此结束,请问有什么问题?

现在我最大的问题是我正在读取一个包含数千行的非常大的 CSV 文件,所以每一个不必要的事情都会显着增加时间。

我的第二个问题是有很多具有相同 ASCII 值的值,我检查一百多个比正常 ascii 值方法有效的方法,但是?为查找空格或同一个单词,Insert 函数每个单词调用自身数百次。
(这导致了最大的问题)。

所以我考虑使用多个哈希表。
例如,我可以检查单词的第一个字母,如果它是
A和E之间,存储在第一个哈希表中
F和J之间,存入第二个哈希表
...
在 V 和 Z 之间,存储在最后一个哈希表中。

再次重要提示:我唯一应该关注的是速度、存储或大小不是问题。

因此,应该通过这种方式最大限度地减少冲突。
我什至可以创建数量惊人的哈希表,并且对于每个不同的字母,我都可以使用不同的哈希表。
但我不确定这是否合乎逻辑,或者我可以使用更简单的方法。

如果可以使用多个哈希表,而不是一个一个地创建哈希表,是否可以创建一个在每个位置存储一个哈希表的数组? (与数组数组相同,但这次数组存储哈希表) 如果可能且合乎逻辑,有人可以展示如何做吗?

这是我的哈希表:

class HashEntry
{
public:
    int key;
    string value;
    HashEntry(int key, string value)
    {
        this->key = key;
        this->value = value;
    }
};

class HashMap
{
private:
    HashEntry * *table;
public:
    HashMap()
    {
        table = new HashEntry *[TABLE_SIZE];
        for (int i = 0; i < TABLE_SIZE; i++)
        {
            table[i] = NULL;
        }
    }

//Functions

}

我很抱歉我问了这么长的问题,如果我不能清楚地解释每个部分,我再次非常抱歉,英语不是我的母语。

最后要注意的是,我这样做是为了一个学校项目,所以我不应该使用任何第三方软件或包含任何不同的库,因为这是不允许的。

【问题讨论】:

  • 是否也禁止使用标准库?
  • @r3musn0x 在项目中写着“(您不应该使用第三方库,包括 C++ STL、Boost 等)。但是,您可以使用 iostream、ctime、fstream、类似字符串的 IO 和字符串相关的类。“所以可能是的,我需要使用自己的哈希表。
  • 为什么要将字数存储在单独的数组而不是哈希表中?
  • @r3musn0x 因为我对数组进行排序比尝试使用多个值的排序哈希表更容易。我认为这不会造成太多时间损失,但如果我错了,我会尝试改变。

标签: c++ count hashtable word


【解决方案1】:

您正在使用一个非常糟糕的哈希函数(添加所有字符),这就是为什么您会遇到如此多的冲突并且您的 Insert 方法因此会多次调用自身。

有关不同哈希函数的详细概述,请参阅this 问题的答案。我建议您尝试 DJB2 或 FNV-1a(在 std::unordered_map 的某些实现中使用)。

您还应该对空白处使用更多本地化的“探针”来改进cache-locality,并在您的Insert 方法中使用循环而不是递归。

但首先我建议你稍微调整一下HashEntry

class HashEntry
{
public:
    string key; // the word is actually a key, no need to store hash value
    size_t value; // the word count is the value.
    HashEntry(string key)
        : key(std::move(key)), value(1) // move the string to avoid unnecessary copying
    { }
};

那我们试试用更好的哈希函数:

// DJB2 hash-function
size_t Hash(const string &key)
{
    size_t hash = 5381;
    for (auto &&c : key)
        hash = ((hash << 5) + hash) + c;
    return hash;
}

然后重写Insert函数:

void Insert(string key)
{
    size_t index = Hash(key) % TABLE_SIZE;

    while (table[index] != nullptr) {       
        if (table[index]->key == key) {               
            ++table[index]->value;
            return;
        }
        ++index;
        if (index == TABLE_SIZE) // "wrap around" if we've reached the end of the hash table
            index = 0;
    }           

    table[index] = new HashEntry(std::move(key));
}

要通过键查找哈希表条目,您可以使用类似的方法:

HashEntry *Find(const string &key)
{
    size_t index = Hash(key) % TABLE_SIZE;

    while (table[index] != nullptr) {       
        if (table[index]->key == key) {               
            return table[index];
        }
        ++index;
        if (index == TABLE_SIZE)
            index = 0;
    }           

    return nullptr;
}

【讨论】:

  • 感谢您的建议并表明我做错了什么,我试图实施您所说的,但我在两部分中遇到错误。第一个“返回值类型与函数类型不匹配”错误“void Hash”和“表达式必须具有整数或非范围枚举类型”错误插入函数行“size_t index = Hash(key) % TABLE_SIZE;” (Hash(key)部分)我在“class HashMap”中写了“void Hash”,也许这就是我做错了。
  • @whiragon,对不起,我的错误。当然Hash 应该返回size_t,我已经更新了代码。
  • @whiragon,我认为您不需要多个哈希表,除非您有几组不同的键。鉴于您只有“映射”到它们各自计数的单词 - 一个哈希表就足够了。例如,如果您需要反向映射(计数 -> 单词),那么您必须为此创建一个单独的哈希表。
  • 使用多个哈希表是否有意义?还是完全没有必要?另外我将如何从表中读取值?我使用的是“string value = table [int value]-> value;”这一行但我不知道我应该如何以新格式做到这一点
  • @whiragon,我已经用示例更新了答案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-04-10
  • 2016-04-25
  • 2015-08-20
  • 1970-01-01
  • 2011-06-14
  • 1970-01-01
相关资源
最近更新 更多