想象两个正整数A和B。我想将它们组合成一个单独的整数C。
没有其他整数D和E可以组合成C。因此,使用加法运算符进行组合不起作用。例如:30 + 10 = 40 = 40 + 0 = 39 + 1,连接操作也不行,例如:“31”+“2”= 312 = “3”+“12”。
这种组合操作还应该是确定性的(对于相同的输入始终产生相同的结果)并且总是产生整数,无论是正整数还是负整数。
想象两个正整数A和B。我想将它们组合成一个单独的整数C。
没有其他整数D和E可以组合成C。因此,使用加法运算符进行组合不起作用。例如:30 + 10 = 40 = 40 + 0 = 39 + 1,连接操作也不行,例如:“31”+“2”= 312 = “3”+“12”。
这种组合操作还应该是确定性的(对于相同的输入始终产生相同的结果)并且总是产生整数,无论是正整数还是负整数。
康托二元组函数是非常好的一种函数之一,因为它简单、快速、空间有效。但是,在Wolfram上有更好的函数,由Matthew Szudzik在这里发表。相对而言,康托二元组函数的局限性是编码结果的范围并不总是在两个N
位整数的限制内。也就是说,如果我的输入是两个16
位整数,范围从0到2^16-1
,那么可能会有2^16 * (2^16 -1)
种输入组合,所以根据显然的鸽巢原理,我们需要至少2^16 * (2^16 -1)
大小的输出,等于2^32-2^16
,或者换句话说,理想情况下应该使用32
位数字的映射。在编程世界中,这可能并不重要。
康托二元组函数:
(a + b) * (a + b + 1) / 2 + a; where a, b >= 0
对于两个最大的16位整数(65535、65535)的映射将是8589803520,正如您所看到的,它不能适合32位。
输入Szudzik函数:
a >= b ? a * a + a + b : a + b * b; where a, b >= 0
(65535, 65535)的映射现在将为4294967295,如您所见,它是一个32位(0到2^32-1)的整数。这就是为什么这个方案是理想的,它简单地利用了该空间中的每一个点,因此没有任何东西可以更加空间高效。
现在考虑到我们通常处理带符号实现的各种大小的数字在语言/框架中,让我们考虑范围从-(2^15)到2^15-1
的有符号16位整数(稍后我们将看到如何扩展输出以跨越已签名的范围)。由于a
和b
必须为正数,它们的范围为0到2^15-1
。
康托对数函数:
两个最大的16位有符号整数(32767,32767)的映射将为2147418112,这仅略小于有符号32位整数的最大值。
现在是Szudzik's function:
(32767,32767) => 1073741823,要小得多...
让我们考虑负整数。我知道这超出了原始问题的范围,但只是为了帮助未来的访问者而阐述。
康托对数函数:
A = a >= 0 ? 2 * a : -2 * a - 1;
B = b >= 0 ? 2 * b : -2 * b - 1;
(A + B) * (A + B + 1) / 2 + A;
(-32768, -32768) => 8589803520,这是Int64类型。对于16位输入的64位输出可能如此不可原谅!!
Szudzik的函数:
A = a >= 0 ? 2 * a : -2 * a - 1;
B = b >= 0 ? 2 * b : -2 * b - 1;
A >= B ? A * A + A + B : A + B * B;
(-32768, -32768) => 4294967295,这是32位无符号范围或64位有符号范围,但仍然更好。现在,在有符号的世界中,如果我们能够将一半的输出转移到负轴上,那么它将更加节省空间。对于Szudzik's,可以像这样实现:A = a >= 0 ? 2 * a : -2 * a - 1;
B = b >= 0 ? 2 * b : -2 * b - 1;
C = (A >= B ? A * A + A + B : A + B * B) / 2;
a < 0 && b < 0 || a >= 0 && b >= 0 ? C : -C - 1;
(-32768, 32767) => -2147483648
(32767, -32768) => -2147450880
(0, 0) => 0
(32767, 32767) => 2147418112
(-32768, -32768) => 2147483647
我的工作:在对输入进行加权(2
),并经过函数处理后,我将输出除以二,并通过乘以-1
将一些输出移动到负轴上。
观察结果,对于任何在有符号 16
位数范围内的输入,输出都在有符号 32
位整数的限制范围内,这很棒。我不确定如何以同样的方式处理Cantor配对函数,但由于效率不高,我没有尝试太多。此外,Cantor配对函数中涉及更多计算意味着它也更慢。
这是一个C#实现。
public static long PerfectlyHashThem(int a, int b)
{
var A = (ulong)(a >= 0 ? 2 * (long)a : -2 * (long)a - 1);
var B = (ulong)(b >= 0 ? 2 * (long)b : -2 * (long)b - 1);
var C = (long)((A >= B ? A * A + A + B : A + B * B) / 2);
return a < 0 && b < 0 || a >= 0 && b >= 0 ? C : -C - 1;
}
public static int PerfectlyHashThem(short a, short b)
{
var A = (uint)(a >= 0 ? 2 * a : -2 * a - 1);
var B = (uint)(b >= 0 ? 2 * b : -2 * b - 1);
var C = (int)((A >= B ? A * A + A + B : A + B * B) / 2);
return a < 0 && b < 0 || a >= 0 && b >= 0 ? C : -C - 1;
}
由于中间计算可能超出2N
有符号整数的限制,我使用了4N
整数类型(最后除以2
可将结果还原为2N
)。
我提供的备选解决方案链接很好地描绘了利用空间中每个点的函数图。令人惊奇的是,您可以将一对坐标唯一编码为一个数字并可逆转!数字的神奇世界!
NxN -> N
映射。这些映射用于例如 回中切割。请查看 此 PDF 以了解所谓的配对函数的介绍。维基百科介绍了一种特定的配对函数,即康托尔配对函数。
三点说明:如果A和B可以用2个字节表示,你可以将它们合并到4个字节上。将A放在最高的一半,B放在最低的一半。
在C语言中,这将得到以下结果(假设sizeof(short)=2且sizeof(int)=4):
unsigned int combine(unsigned short A, unsigned short B)
{
return ((unsigned)A<<16) | (unsigned)B;
}
unsigned short getA(unsigned int C)
{
return C>>16;
}
unsigned short getB(unsigned int C)
{
return C & 0xFFFF; // or return (unsigned short)C;
}
将输入类型设置为 unsigned short
或 uint16_t
,确保在进行 |
或 +
运算之前进行零扩展。否则,使用负的 B
值时,OR 操作将会将高位设置为全 1,而 ADD 操作将会使高半部分减去 1。
强制转换为 (unsigned)A
可以避免窄类型在默认升级为有符号 int
后发生有符号溢出 UB。对于更宽的类型,也必须避免移位时丢失掉需要保留的位,例如 ((uint64_t)A << 32 | B)
,因为默认提升停止于 int
。
强制转换为 (unsigned)B
并不是必需的;重要的是它最初是 unsigned short B
类型。左侧的 |
是 unsigned
类型,这意味着它也会转换为 unsigned
。
您可以在带符号类型中使用此方法,至少可以从 getA
和 getB
中获得有符号类型,并且可以从 combine
返回有符号 int
,但输入需要进行零扩展,因此在 C 中您需要将它们设置为 unsigned short
。例如: ((unsigned)(unsigned short)A << 16) | (unsigned short)B
建议使用 uint16_t
和 uint32_t
,以定义类型宽度以匹配您使用的移位计数。
combine()
应该返回(unsigned short)(A<<16) | (unsigned short)(B);
,以便能够正确地打包负数。 - AndyA<<16
操作会越界,应该改为 return (unsigned int)(A<<16) | (unsigned short)(B);
。 - DanSkeel这真的可能吗?
您正在组合两个整数。它们的范围都为-2,147,483,648至2,147,483,647,但您只会选取正数。
这使得2147483647^2 = 4.61169E+18 种组合。
由于每个组合必须是唯一的,并且结果为整数,因此您需要某种可以包含这么多数字的神奇整数。
或者我的逻辑有误吗?
假设数字a
是第一个,b
是第二个。假设p
是第a+1
个质数,q
是第b+1
个质数。
那么,如果a<b
,结果就是pq
;如果a>b
,结果为2pq
。如果a=b
,结果为p^2
。
对于正整数,标准的数学方法是使用质因数分解的唯一性。
f( x, y ) -> 2^x * 3^y
缺点是图像往往涵盖了相当大的整数范围,因此在编写计算机算法时,您可能会遇到选择适当类型的问题。
您可以通过使用5和7次幂项来编码标志来修改此内容以处理负的x
和y
。
例如:
f( x, y ) -> 2^|x| * 3^|y| * 5^(x<0) * 7^(y<0)
尽管Stephan202的答案是唯一真正通用的,但对于有限范围内的整数,您可以做得更好。例如,如果您的范围是0..10,000,则可以执行以下操作:
#define RANGE_MIN 0
#define RANGE_MAX 10000
unsigned int merge(unsigned int x, unsigned int y)
{
return (x * (RANGE_MAX - RANGE_MIN + 1)) + y;
}
void split(unsigned int v, unsigned int &x, unsigned int &y)
{
x = RANGE_MIN + (v / (RANGE_MAX - RANGE_MIN + 1));
y = RANGE_MIN + (v % (RANGE_MAX - RANGE_MIN + 1));
}
结果可以适合一个整数范围,最多达到整数类型基数的平方根。这比Stephan202的更普遍的方法稍微更有效地打包。它也更容易解码,起步不需要平方根 :)
fmod
恢复的数字y
,会被从一个更大的数字x * range
中加上的舍入误差所“污染”。将一个小数加到一个大数上会丢失大部分较小数的精度。想想FP加法的工作原理:将它们的尾数移位以对齐它们(基于指数),然后相加,并截断为尾数宽度(实际上是四舍五入)。因此,你会移出许多y
的有效位。 - Peter Cordes对于正整数作为参数且参数顺序不重要的情况:
Here's an unordered pairing function:
<x, y> = x * y + trunc((|x - y| - 1)^2 / 4) = <y, x>
For x ≠ y, here's a unique unordered pairing function:
<x, y> = if x < y:
x * (y - 1) + trunc((y - x - 2)^2 / 4)
if x > y:
(x - 1) * y + trunc((x - y - 2)^2 / 4)
= <y, x>
f(a, b) = s(a+b) + a
,其中s(n) = n*(n+1)/2
s(a+b+1)-s(a+b) = a+b+1<a
。我不明白您的意思:
应该始终在整数的正侧或负侧产生整数
我该如何在这个论坛中写入“大于”、“小于”符号?
反引号转义
中正常工作。 - TRiG#!/usr/bin/env python
from numbers import Integral
def tuple_to_int(tup):
""":Return: the unique non-negative integer encoding of a tuple of non-negative integers."""
if len(tup) == 0: # normally do if not tup, but doesn't work with np
raise ValueError('Cannot encode empty tuple')
if len(tup) == 1:
x = tup[0]
if not isinstance(x, Integral):
raise ValueError('Can only encode integers')
return x
elif len(tup) == 2:
# print("len=2")
x, y = tuple_to_int(tup[0:1]), tuple_to_int(tup[1:2]) # Just to validate x and y
X = 2 * x if x >= 0 else -2 * x - 1 # map x to positive integers
Y = 2 * y if y >= 0 else -2 * y - 1 # map y to positive integers
Z = (X * X + X + Y) if X >= Y else (X + Y * Y) # encode
# Map evens onto positives
if (x >= 0 and y >= 0):
return Z // 2
elif (x < 0 and y >= 0 and X >= Y):
return Z // 2
elif (x < 0 and y < 0 and X < Y):
return Z // 2
# Map odds onto negative
else:
return (-Z - 1) // 2
else:
return tuple_to_int((tuple_to_int(tup[:2]),) + tuple(tup[2:])) # ***speed up tuple(tup[2:])?***
def int_to_tuple(num, size=2):
""":Return: the unique tuple of length `size` that encodes to `num`."""
if not isinstance(num, Integral):
raise ValueError('Can only encode integers (got {})'.format(num))
if not isinstance(size, Integral) or size < 1:
raise ValueError('Tuple is the wrong size ({})'.format(size))
if size == 1:
return (num,)
elif size == 2:
# Mapping onto positive integers
Z = -2 * num - 1 if num < 0 else 2 * num
# Reversing Pairing
s = isqrt(Z)
if Z - s * s < s:
X, Y = Z - s * s, s
else:
X, Y = s, Z - s * s - s
# Undoing mappint to positive integers
x = (X + 1) // -2 if X % 2 else X // 2 # True if X not divisible by 2
y = (Y + 1) // -2 if Y % 2 else Y // 2 # True if Y not divisible by 2
return x, y
else:
x, y = int_to_tuple(num, 2)
return int_to_tuple(x, size - 1) + (y,)
def isqrt(n):
"""":Return: the largest integer x for which x * x does not exceed n."""
# Newton's method, via https://dev59.com/3WUp5IYBdhLWcg3wRV9D#15391420
x = n
y = (x + 1) // 2
while y < x:
x = y
y = (x + n // x) // 2
return x
10,001*A + B
吗? - BlueRaja - Danny Pflughoeft