特定有限整数集的有效映射

时间:2011-02-06 06:50:51

标签: c unicode mapping bit-manipulation

我正在寻找下面的整数列表与0-127范围的子集之间的一个小的,快速的(双向)双射映射:

0x200C, 0x200D, 0x200E, 0x200F,
0x2013, 0x2014, 0x2015, 0x2017,
0x2018, 0x2019, 0x201A, 0x201C,
0x201D, 0x201E, 0x2020, 0x2021,
0x2022, 0x2026, 0x2030, 0x2039,
0x203A, 0x20AA, 0x20AB, 0x20AC,
0x20AF, 0x2116, 0x2122

一个明显的解决方案是:

y = x>>2 & 0x40 | x & 0x3f;
x = 0x2000 | y<<2 & 0x100 | y & 0x3f;

编辑:我遗漏了一些值,特别是0x20Ax,这些值与上述不兼容。

另一个显而易见的解决方案是查找表,但不会使其不必要地大,查找表无论如何都需要一些位重排,我怀疑通过简单的位重排可以更好地完成整个任务。

对于好奇的人来说,这些神奇的数字是传统ISO-8859和Windows代码页中唯一出现的“大”Unicode代码点。

4 个答案:

答案 0 :(得分:3)

此方法在有限域中使用乘法:

#define PRIME 0x119
#define OFFSET1 0x00f
#define OFFSET2 0x200c
#define OFFSET3 (OFFSET2 - OFFSET1)
#define MULTIPLIER 2
#define INVERSE 0x8d

unsigned map(unsigned n)
{
    return ((n - OFFSET3) * MULTIPLIER) % PRIME;
}

unsigned unmap(unsigned m)
{
    return ((m * INVERSE) + PRIME - OFFSET1) % PRIME + OFFSET2;
}

map()将unicode点转换为唯一的7位数字,而unmap()则相反。请注意,gcc至少能够将此编译为不使用任何除法运算的x86代码,因为模数是常数。

答案 1 :(得分:1)

我知道它很难看,但除了最后一个值之外,如果考虑最低6位,所有其他的都已经是唯一的,所以你可以构建和反向映射:

int ints[] = {0x200C, 0x200D, 0x200E, 0x200F,
              0x2013, 0x2014, 0x2015, 0x2017,
              0x2018, 0x2019, 0x201A, 0x201C,
              0x201D, 0x201E, 0x2020, 0x2021,
              0x2022, 0x2026, 0x2030, 0x2039,
              0x203A, 0x20AA, 0x20AB, 0x20AC,
              0x20AF, 0x2116, 0x2122};

int invmap[64];

void mkinvmap()
{
    for (int i=0; i<26; i++)
        invmap[ints[i]&63] = ints[i];
    invmap[0] = 0x2122;
}

在这个逆映射计算之后,两个变换函数是

int direct(int x)  { return x==0x2122 ? 0 : (x & 63); }
int inverse(int x) { return invmap[x]; }

函数direct(x)将返回0到63之间的数字,并且给定0到63之间的数字的函数inverse(x)将返回一个整数。对于列表inverse(direct(x)) == x中的所有27个值。

答案 2 :(得分:1)

我会选择一些简单(且便宜)的哈希函数f,您可以选择这些函数的f0, f1, ...0..255映射到值wchar_t const uniqTable[91] = { [0x7] = L'\u2116' /* № */, [0xD] = L'\uFFFD' /* � */, [0xE] = L'\u200C' /* ‌ */, [0xF] = L'\u200D' /* ‍ */, [0x10] = L'\u200E' /* ‎ */, [0x11] = L'\u200F' /* ‏ */, [0x13] = L'\u2122' /* ™ */, [0x15] = L'\u2013' /* – */, [0x16] = L'\u2014' /* — */, [0x17] = L'\u2015' /* ― */, [0x19] = L'\u2017' /* ‗ */, [0x1A] = L'\u2018' /* ‘ */, [0x1B] = L'\u2019' /* ’ */, [0x1C] = L'\u201A' /* ‚ */, [0x1E] = L'\u201C' /* “ */, [0x1F] = L'\u201D' /* ” */, [0x20] = L'\u201E' /* „ */, [0x22] = L'\u2020' /* † */, [0x23] = L'\u2021' /* ‡ */, [0x24] = L'\u2022' /* • */, [0x28] = L'\u2026' /* … */, [0x32] = L'\u2030' /* ‰ */, [0x3B] = L'\u2039' /* ‹ */, [0x3C] = L'\u203A' /* › */, [0x51] = L'\u20AA' /* ₪ */, [0x52] = L'\u20AB' /* ₫ */, [0x53] = L'\u20AC' /* € */, [0x56] = L'\u20AF' /* ₯ */, }; 。如果你的哈希函数是随机的,那么生日悖论你就会对你感兴趣的值产生一些冲突,但不是很多。

现在,一个简单的perl(无论如何)脚本将允许您预处理固定值数据,以通过从集合中选择适当的函数来减少(甚至消除)冲突。

这种方法的优点是,如果您发现忘记了某个值(正如您已经做过的那样),或者某个奇怪的国家/地区决定将奇怪的unicode字符(如€)映射到8位字符集,则可以续订预处理运行。

而且,BTW,我认为一些iso-8859-中的特殊字符数量?套装必须比你拥有的大得多,在这里,不是吗?我会把它们都带走。

编辑:在做了一些实验之后,一个小的perl脚本告诉我,当减少模数10007或10009时,出现在iso-8859编码之一中的所有577个unicode代码点都映射到不同的位置。 / p>

编辑:对于有限的集合,下表可以解决这个问题:

{{1}}

答案 3 :(得分:0)

通过试用&amp;错误,我得到了以下算法:

#include <assert.h>
#include <stdio.h>

static const unsigned CODES[] = {
    0x200C, 0x200D, 0x200E, 0x200F,
    0x2013, 0x2014, 0x2015, 0x2017,
    0x2018, 0x2019, 0x201A, 0x201C,
    0x201D, 0x201E, 0x2020, 0x2021,
    0x2022, 0x2026, 0x2030, 0x2039,
    0x203A, 0x20AA, 0x20AB, 0x20AC,
    0x20AF, 0x2116, 0x2122
};

static unsigned enc(unsigned value)
{
    return (value & 0x3F) + (value & 0x180) / 4;
}

static unsigned dec(unsigned value)
{
    return 0x2000 + value + ((value & 0x40) >> 6) * 3 *
        (0x20 + (value & 0x10) * 2 + (value & 0x20));
}

int main(void)
{
    const unsigned *const END = CODES + sizeof CODES / sizeof *CODES;
    const unsigned *current = CODES;
    for(; current < END; ++current)
    {
        printf("%04x -> %02x -> %04x\n",
            *current, enc(*current), dec(enc(*current)));

        assert(enc(*current) < 0x80);
        assert(dec(enc(*current)) == *current);
    }

    return 0;
}

有时候,即使在编写代码时,进化也会超越智能设计;)