我正在寻找下面的整数列表与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代码点。
答案 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;
}
有时候,即使在编写代码时,进化也会超越智能设计;)