如何实现轻量级快速关联数组

How to implement a lightweight fast associative array?

本文关键字:关联 数组 轻量级 何实现 实现      更新时间:2023-10-16

我正在尝试了解如何实现一个为搜索操作提供恒定时间的关联数组,现在我的实现如下所示:

#include <iostream>
#include <vector>
#include <string>
using namespace std;
template <class Key, class Value> class Dict {
private:
typedef struct Item {
Value value;
Key key;
} Item;
vector<Item> _data;
public:
void clear() {
_data.clear();
}
long size() {
return _data.size();
}
bool is_item(Key key) {
for (int i = 0; i < size(); i++) {
if (_data[i].key == key) return true;
}
return false;
}
bool add_item(Key key, Value value) {
if (is_item(key)) return false;
Item new_item;
new_item.key = key;
new_item.value = value;
_data.push_back(new_item);
return true;
}
Value &operator[](Key key) {
for (int i = 0; i < size(); i++) {
if (_data[i].key == key) return _data[i].value;
}
long idx = size();
Item new_item;
new_item.key = key;
_data.push_back(new_item);
return _data[idx].value;
}
Key get_key(long index) {
if (index < 0) index = 0;
for (int i = 0; i < size(); i++)
if (i == index) return _data[i].key;
return NULL;
}
Value &operator[](long index) {
if (index < 0) index = 0;
for (int i = 0; i < size(); i++) {
if (i == index) return _data[i].value;
}
return _data[0].value;
}
};

对此进行一个简单的测试:

class Foo {
public:
Foo(int value) {
_value = value;
}
int get_value() {
return _value;
}
void set_value(int value) {
_value = value;
}
private:
int _value;
};
template <class Key, class Value> void print_dict(Dict<Key, Value> &dct) {
if (!dct.size()) {
printf("Empty Dict");
}
for (int i = 0; i < dct.size(); i++) {
printf("%d%s", dct[dct.get_key(i)], i == dct.size() - 1 ? "" : ", ");
}
printf("n");
}
int main(int argc, char *argv[]) {
printf("nDict testsn------------n");
Dict<string, int> dct;
string key1("key1");
string key2("key2");
string key3("key3");
dct["key1"] = 100;
dct["key2"] = 200;
dct["key3"] = 300;
printf("%d %d %dn", dct["key1"], dct["key2"], dct["key3"]);
printf("%d %d %dn", dct[key1], dct[key2], dct[key3]);
print_dict(dct);
dct.clear();
print_dict(dct);
Dict<Foo *, int> dct2;
Foo *f1 = new Foo(100);
Foo *f2 = new Foo(200);
dct2[f1] = 101;
dct2[f2] = 202;
print_dict(dct2);
}

事情是这样的,现在搜索操作是线性时间,我希望它成为恒定时间,我想知道一种简单/轻量级的方法来实现这一目标。

我已经看到哈希表是一种可能的选择,但我不希望为每个对象实现哈希函数。也许类似于unordered_map...邓诺。

任何人都可以给出一些想法,或者提供我在这里试图实现的简单轻量级实现吗?

在这个虚构的例子中,我使用 std::vector 来避免使问题比它本身更大、更复杂,但我真正的用例根本不会使用 STL(即:我将编写我自己的自定义实现 std::vector)

约束

  • 根本不使用 STL 的原因不是因为该实现不够好(快速、通用、功能齐全),而是因为对于我的大小受限项目来说非常繁重(最终 exe <=65536bytes)。即使是STL的这个小实现实际上也很大,可以按原样使用。
  • 我不需要关联数组的完整实现,而只需要提供我已经在上面实现的接口(主要问题是线性时间搜索)
  • 我不在乎插入/删除方法很慢,但我绝对希望搜索/查找接近恒定时间
  • 我想我需要使用哈希表在关联数组中转换上述实现,但我不确定相关的实现细节(每个对象的哈希函数,表大小......

让我解决您在问题中提出的一些问题。

事情是这样的,现在搜索操作是线性时间,我希望它成为恒定时间,我想知道一种简单/轻量级的方法来实现这一目标。

实现此目的的一种简单的轻量级方法,即拥有一个关联数组(又名键值存储),是使用标准库提供的数组。

您正在使用最新版本的C++进行编码,您很幸运,标准库实际上提供了一个满足常量时间要求的库:

  • http://en.cppreference.com/w/cpp/container/unordered_map

如今,作为任何体面编译器的标准库的一部分提供的数据结构的实现可能比你能想到的任何东西都要好。(或者你为什么要求给我代码

我已经看到哈希表是一种可能的选择,但我不希望为每个对象实现哈希函数。也许类似于unordered_map...邓诺。

std::unordered_map实际上是一个哈希表,正如您在文档中看到的那样,它需要一个哈希函数。正如您在文档中看到的那样,已经有许多类型的专业化,可以帮助您为自定义对象类型派生自定义哈希函数:

  • http://en.cppreference.com/w/cpp/utility/hash

任何人都可以给出一些想法,或者提供我在这里试图实现的简单轻量级实现吗?

只需查看示例代码即可std::unordered_map了解它的使用方式。如果您担心性能,请不要忘记测量。如果你真的想在哈希表的实现上消耗一些输入,我喜欢Python字典上的这些演讲:

  • https://www.youtube.com/watch?v=C4Kc8xzcA68
  • https://www.youtube.com/watch?v=p33CVV29OG8

另请查看维基百科页面(如果您还没有):

  • https://en.wikipedia.org/wiki/Associative_array

在这个虚构的例子中,我使用 std::vector 来避免使问题比它本身更大、更复杂,但我真正的用例根本不会使用 STL(即:我将编写我自己的自定义实现 std::vector)

除非您出于教育/娱乐目的这样做,否则不要这样做。不要羞于将自己的努力建立在巨人的肩膀上。标准库不是在您的项目中发明的,这不是问题。

如果要保持较小的代码大小,则应尽可能避免使用模板。至少是创建大量代码的模板。

对于您的哈希映射,这意味着:坚持使用一种键类型,并且仅存储指向值void指针。如果您不想在代码中处理void*和随之而来的强制转换,请实现一个非模板哈希映射,该哈希映射将void*存储为值,其中包含所有"无内联"函数。然后创建一个"全内联"(甚至可能是所有"强制内联")包装类,该类在内部使用void*映射并仅转换T*<->void*

如果你真的真的需要不同的密钥类型,看看你是否可以坚持使用没有填充的 POD(memcpy可复制的,memcmp可比的)。这样,您仍然可以对所有内容使用相同的哈希映射类:您只需要告诉映射(在运行时)键大小是多少。然后,您可以使用memcpy将密钥复制到映射中,使用memcmp进行比较,并使用任何可以哈希字节序列的哈希算法(=几乎所有哈希算法)对它们进行哈希处理。

当然,您还需要做很多其他事情,例如避免内联任何非平凡的函数,避免C-Runtime 库函数,禁用异常处理和RTTI等,但这是一个不同的主题。

或者,也许只是坚持普通的C:)