【问题标题】:How to implement a lightweight fast associative array?如何实现轻量级的快速关联数组?
【发布时间】:2017-08-30 20:48:11
【问题描述】:

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

#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...不知道。

谁能提供一些想法,或者提供一个简单的轻量级实现来实现我在这里尝试实现的目标?

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

约束

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

【问题讨论】:

  • std::unordered_map 有什么问题?你这样做是为了编程挑战吗?或许可以解释一下为什么你需要这个新容器。
  • @BPL 没有描述性并不是 _T0 和 _T1 的唯一问题。它们是保留标识符,因此程序将具有未定义的行为。
  • @BPL 仅仅因为某一时刻在一台机器上的一次测试给出了预期的输出,并不意味着程序是正确的,或者在不同的情况下输出是正确的。如果你的程序违反了标准,它就如履薄冰。您的新标识符仍然保留。欲了解更多信息:en.cppreference.com/w/cpp/language/identifiers
  • @BPL "以下划线后跟大写字母的标识符被保留;"
  • 渐近地我很确定保证O(1)访问时间的唯一方法是拥有一个非冲突哈希函数。 (或者,至少对碰撞有一些硬性限制)

标签: c++ associative-array


【解决方案1】:

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

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

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

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

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

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

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

谁能提供一些想法,或者提供一个简单的轻量级实现来实现我在这里尝试实现的目标?

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

还可以查看维基百科页面(如果您还没有的话):

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

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

【讨论】:

  • 感谢您的提示,除了 youtube 之外,我几乎都知道。无论如何,我都会赞成你的回答,因为你提供了非常好的建议,但我不会验证它。我想这是我的错,因为我的问题的限制不是太明确。确实,我已经说过 我的真实用例不会使用 STL,但我没有说明原因,一个原因是因为了解更多关于实现我的自定义和第二个用于64kb_intro。编辑问题以提供更多详细信息...
【解决方案2】:

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

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

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

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

或者,也许只是坚持纯 C :)

【讨论】:

    猜你喜欢
    • 2011-01-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-20
    • 1970-01-01
    • 1970-01-01
    • 2017-01-03
    • 1970-01-01
    相关资源
    最近更新 更多