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

时间:2017-04-05 09:55:00

标签: c++ associative-array

我正在试图理解我应该如何实现一个为搜索操作提供恒定时间的关联数组,现在我的实现看起来像这样:

#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 tests\n------------\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 %d\n", dct["key1"], dct["key2"], dct["key3"]);
    printf("%d %d %d\n", 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);
}

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

我已经看到hashtables是一个可能的选项,但我不想在每个对象上实现哈希函数。也许类似于unordered_map ... dunno。

有人能提出一些想法,或者提供一个简单的轻量级实现,我想在这里实现的目标吗?

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

约束

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

2 个答案:

答案 0 :(得分:3)

让我谈谈你在问题中提出的一些问题。

  

这就是问题,现在搜索操作是线性时间,我希望它成为一个恒定的时间,我想知道一个简单/轻量级的方法来实现这一点。

实现此目的的一种简单轻量级方法,即具有关联数组(a.k.a.key-value-store),是使用标准库提供的方法。

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

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

  

我看过哈希表是一种可能的选择,但我不想在每个对象上实现哈希函数。也许类似于unordered_map ... dunno。

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

  

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

只需查看std::unordered_map的示例代码,了解它是如何使用的。如果您担心表现,请不要忘记衡量。如果你真的想在哈希表的实现上消耗一些输入,我喜欢Python字典上的这些讨论:

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

  

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

除非您出于教育/娱乐目的而这样做,否则不要这样做。不要以你的努力为基础感到羞耻on the shoulders of giants。标准库wasn't invented in your project不是问题。

答案 1 :(得分:0)

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

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

如果您真的真的真的需要其他键类型,请查看您是否可以坚持使用POD而无需填充({memcpy可复制和memcmp可比)。这样,您仍然可以对所有内容使用相同的哈希映射类:您只需(在运行时)告诉映射键大小是什么即可。然后,您可以使用memcpy将密钥复制到地图中,使用memcmp比较密钥,并使用可以对字节序列进行哈希处理的任何哈希算法(几乎每个哈希算法)对它们进行哈希处理。

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

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