用于将连续整数映射到非独特整数的散列函数

时间:2017-08-27 03:24:48

标签: hash embedded lookup-tables

我有一个1760个整数的序列,范围从129到250,并且这些整数没有可辨别的模式。我正在研究一个非常小的嵌入式系统,并且不能在查找表上浪费近2 KB。我想提出一个函数,允许我查找给定索引的值(在0到1759范围内)。

我知道minimal perfect hashing允许我将不同的值映射到一组连续的整数上,但我希望将一组连续的整数映射到非不同的值。

数百万年的蛮力是唯一可行的方法吗?是否有一些方法可以允许更小的查找表(例如,大约256字节或更少)?

1 个答案:

答案 0 :(得分:2)

什么过程会生成1760个整数?遗憾的是,如果不了解您的数据来源,就很难(如您所说,"数百万年")找到这样的功能(如果存在的话)。 Claude Shannon证明随机噪声处于最大信息熵,因此无法压缩。因此,如果整数没有可辨别的模式,那确实可以作为随机噪声。

回到查找表,你可以通过识别你的整数都在129-250范围内,只需要7位来表示,就可以将表的大小减小1/8。通过表查找中的一些位操作技巧,您只需要1760 * 7/8 = 1540字节或12.5%的节省。它不是很多,但它是一个开始;这里有一些示例代码来说明我的意思。

示例代码

#include <cassert>
#include <cstdint>
#include <iomanip>
#include <iostream>
#include <vector>

void compress(const std::vector<uint8_t>& raw, std::vector<uint8_t>& comp) {
    // Length must be a multiple of 8 to handle unrolled loop.
    assert(raw.size() % 8 == 0);

    comp.resize(raw.size() * 7 / 8);
    for (size_t rIdx = 0, cIdx = 0; rIdx < raw.size(); rIdx += 8, cIdx += 7) {
        comp[cIdx + 0] = (raw[rIdx + 0] << 1) | ((raw[rIdx + 1] & 0x7f) >> 6);
        comp[cIdx + 1] = (raw[rIdx + 1] << 2) | ((raw[rIdx + 2] & 0x7f) >> 5);
        comp[cIdx + 2] = (raw[rIdx + 2] << 3) | ((raw[rIdx + 3] & 0x7f) >> 4);
        comp[cIdx + 3] = (raw[rIdx + 3] << 4) | ((raw[rIdx + 4] & 0x7f) >> 3);
        comp[cIdx + 4] = (raw[rIdx + 4] << 5) | ((raw[rIdx + 5] & 0x7f) >> 2);
        comp[cIdx + 5] = (raw[rIdx + 5] << 6) | ((raw[rIdx + 6] & 0x7f) >> 1);
        comp[cIdx + 6] = (raw[rIdx + 6] << 7) | ((raw[rIdx + 7] & 0x7f) >> 0);
    }
}

uint8_t lookup(const std::vector<uint8_t>& comp, size_t rIdx) {
    size_t cIdx = rIdx / 8 * 7;
    switch (rIdx % 8) {
    case 0:
        return                                  (comp[cIdx + 0] >> 1) | 0x80;
    case 1:
        return ((comp[cIdx + 0] & 0x01) << 6) | (comp[cIdx + 1] >> 2) | 0x80;
    case 2:
        return ((comp[cIdx + 1] & 0x03) << 5) | (comp[cIdx + 2] >> 3) | 0x80;
    case 3:
        return ((comp[cIdx + 2] & 0x07) << 4) | (comp[cIdx + 3] >> 4) | 0x80;
    case 4:
        return ((comp[cIdx + 3] & 0x0f) << 3) | (comp[cIdx + 4] >> 5) | 0x80;
    case 5:
        return ((comp[cIdx + 4] & 0x1f) << 2) | (comp[cIdx + 5] >> 6) | 0x80;
    case 6:
        return ((comp[cIdx + 5] & 0x3f) << 1) | (comp[cIdx + 6] >> 7) | 0x80;
    case 7:
        return ((comp[cIdx + 6] & 0x7f) << 0) | 0x80;
    }
}

int main() {
    std::vector<uint8_t> raw { 151, 169, 162, 164, 155, 147, 149, 143, };
    std::vector<uint8_t> comp;

    compress(raw, comp);

    for (size_t i = 0; i < raw.size(); ++i) {
        std::cout << i << ": raw " << static_cast<int>(raw[i])
                  << ", lookup " << static_cast<int>(lookup(comp, i))
                  << std::endl;
    }
    return 0;
}

输出

我只是在每个索引处打印出原始和压缩/未压缩的数据,以验证存储和检索。

0: raw 151, lookup 151
1: raw 169, lookup 169
2: raw 162, lookup 162
3: raw 164, lookup 164
4: raw 155, lookup 155
5: raw 147, lookup 147
6: raw 149, lookup 149
7: raw 143, lookup 143

如果您的输入数据长度不再是8的倍数,那么还有一些工作要做,但这应该可以让您开始。