以唯一和确定性的方式将两个整数映射为一个


235

想象两个正整数A和B。我想将这两个整数合并为一个整数C。

不能有其他整数D和E组合成C。因此,将它们与加法运算符组合起来是行不通的。例如,30 + 10 = 40 = 40 + 0 = 39 + 1叠加都不起作用。例如,“ 31” +“ 2” = 312 =“ 3” +“ 12”

此组合运算还应该是确定性的(始终在相同的输入下产生相同的结果),并且应始终在整数的正或负侧产生整数。


10
您应该澄清是指软件中的整数还是数学中的整数。在软件中,您选择任何整数类型,它都会有一个大小,因此它们的数量是有限的,所以没有解决方案(当然,除非您保证输入数据在一定范围内,并且您的输出可以是任何整数)。在数学上请参阅ASk的解决方案。
丹尼尔·达拉纳斯

我说的是低正数范围内的有界整数。说出0到10,000
伤害

27
@harm:那又如何10,001*A + B呢?
BlueRaja-Danny Pflughoeft

2
我发现了以下PHP函数:gist.github.com/hannesl/8031402
cakan

如果顺序无关紧要,例如:(3,12)&(12,3)给出相同的结果,则我使用“ A + B” +“ A * B”
Sodj 18'May

Answers:


233

您正在寻找双射NxN -> N映射。这些用于例如燕尾加工。查看此PDF,了解所谓的配对功能。Wikipedia引入了特定的配对功能,即Cantor配对功能

pi(k1, k2) = 1/2(k1 + k2)(k1 + k2 + 1) + k2

三句话:

  • 正如其他人已经明确指出的那样,如果您打算实现配对功能,您很快就会发现您需要任意大的整数(bignums)。
  • 如果不想在(a,b)和(b,a)对之间进行区分,请在应用配对功能之前对a和b进行排序。
  • 其实我说谎 您正在寻找双射ZxZ -> N映射。Cantor函数仅适用于非负数。但是,这不是问题,因为定义双射很容易f : Z -> N,就像这样:
    • 如果n> = 0,则f(n)= n * 2
    • 如果n <0,则f(n)= -n * 2-1

13
+1我认为这是无界整数的正确答案。
未知

4
我怎样才能再次得到k1,k2的值?
MinuMaster 2012年

3
@MinuMaster:在同一个Wikipedia文章中,在反转Cantor配对函数下进行了描述。
Stephan202

4
另请参见Szudzik的函数,在下面的newfal中进行了说明。
OliJG 2012年

1
尽管这对于无界整数是正确的,但对于有界整数并不是最佳选择。我认为@ blue-raja的评论到目前为止最有意义。
Kardasis

226

考虑到Cantor配对功能的简单,快速和节省空间的特性,它确实是其中最好的配对功能,但是Matthew Szudzik在Wolfram上发表的文章甚至更好。Cantor配对函数的局限性(相对)是,2N如果输入是两位整数,则编码结果的范围并不总是保持在一位整数的范围内N。也就是说,如果我的输入是16从到的两位整数0 to 2^16 -1,则2^16 * (2^16 -1)可能存在输入的组合,因此根据显而易见的Pigeonhole原理,我们需要输出的大小至少为2^16 * (2^16 -1),等于2^32 - 2^16,或者换句话说,是32理想地,位数应该是可行的。这在编程世界中可能并非没有什么实际意义。

Cantor配对功能

(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)的整数。这是该解决方案的理想之选,它只是利用了该空间中的每个点,因此没有什么可以提高空间效率的。


现在考虑到我们通常处理语言/框架中各种大小的数字的带符号实现这一事实,让我们考虑signed 16范围为的位整数-(2^15) to 2^15 -1(稍后我们将看到如何甚至扩展输出以跨越符号范围)。既然ab必须是肯定的,它们的范围是0 to 2^15 - 1

Cantor配对功能

两个最大的最多16位带符号整数(32767、32767)的映射将为2147418112,这刚好小于带符号的32位整数的最大值。

现在 Szudzik的功能是

(32767,32767)=> 1073741823,要小得多。

让我们考虑负整数。这超出了我所知道的原始问题,但仅是为了帮助将来的访问者。

Cantor配对功能

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,您可以这样做:

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)。

我在替代解决方案上提供的链接很好地描绘了利用空间中每个点的函数图。令人惊讶的是,您可以将一对坐标唯一地可逆编码为单个数字!数字魔术世界!


5
带符号整数的修改后的unhash函数将是什么?
Arets Paeglis 2013年

7
这个答案使我感到困惑。如果要映射(0,0)(65535,65535)单个数字,则a<<16 + b基本上在每种方式上都更好(更快,更简单,更易于理解,更明显)。如果你想(-32768,-32768)(327687,327687)代替,只是受到32768首位。
BlueRaja-Danny Pflughoeft

2
@ BlueRaja-DannyPflughoeft你是对的。如果范围不受限制或未知,我的答案将是有效的。我会更新。我已经写了本书,而后才对我很重要。长期以来,我一直想编辑此答案。我很快就会找到时间。
2014年

Szudzik的函数是否可用于组合或排列。似乎是排列对吗?如果要用于组合,是否可以消除算法的IF和其他部分?
杰米·马歇尔

这是Szudzik函数的Python实现,可广义化为任意长度的元组:gitlab.com/snippets/32559
J博士,

47

如果A和B可以用2个字节表示,则可以将它们组合成4个字节。将A放在最重要的一半,B放在最不重要的一半。

在C语言中,这给出了(假设sizeof(short)= 2和sizeof(int)= 4):

int combine(short A, short B)
{
    return A<<16 | B;
}

short getA(int C)
{
    return C>>16;
}

short getB(int C)
{
    return C & 0xFFFF;
}

3
combine()应该return (unsigned short)(A<<16) | (unsigned short)(B); 这样,以便负数可以正确打包。
安迪

2
@Andy A<<16会越界。应该是return (unsigned int)(A<<16) | (unsigned short)(B);
DanSkeel

15

这有可能吗?
您正在组合两个整数。它们都在-2,147,483,648到2,147,483,647之间,但是您只会取正数。这使得2147483647 ^ 2 = 4,61169E + 18组合。由于每个组合必须是唯一的并且要产生一个整数,因此您需要某种可以包含此数量数字的神奇整数。

还是我的逻辑有缺陷?


+1这也是我的想法(尽管我做了计算,说A和B的顺序无关紧要)
lc。

4
是的,根据信鸽原则,您的逻辑是正确的。不幸的是,问问者未指定整数是否有界。
未知

是的,我也有这种想法,但是我认为该消息在本质上是相同的,因此我不必理会重新计算。
鲍里斯·卡伦斯

我也刚刚意识到我应该再次学习机会计算(荷兰语的笔译)教科书。
鲍里斯·卡伦斯

2
@鲍里斯:Kansrekening是“概率论”。
Stephan202

8

正整数的标准数学方法是使用素数分解的唯一性。

f( x, y ) -> 2^x * 3^y

不利的一面是图像倾向于跨越很大范围的整数,因此在计算机算法中表达映射时,您可能会在为结果选择合适的类型时遇到问题。

您可以修改此值以处理负数,xy用5和7项的幂对标志进行编码。

例如

f( x, y ) -> 2^|x| * 3^|y| * 5^(x<0) * 7^(y<0)

数学很好。但是,正如鲍里斯(Boris)所说,如果要将此程序作为计算机程序运行,则必须考虑机器的局限性。对于相关机器中可表示的整数子集,该算法将正确运行。
Yuval F

2
我在第二段中确实指出了这一点。问题上的标签表示“算法”,“数学”和“确定性”,而不是任何特定语言。输入范围可能不受限制,环境可能具有无界的整数类型“ bigint”。
CB Bailey

8

设数字a为第一个,b第二个。让pa+1个质数,qb+1个质数

然后,结果为pqif a<b,2pqif a>b。如果a=b是的话p^2


4
我怀疑您是否需要NP解决方案。
user44242

1
对于a = 5,b = 14和a = 6,b = 15,这不会产生相同的结果吗?
Lieven Keersmaekers,2009年

3
两个不同素数的两个乘积不能具有相同的结果(唯一素因数分解)a = 5,b = 14->结果为13 * 47 = 611 a = 6,b = 15->结果为17 * 53 = 901
ASk

4

构造映射并不难:

   1 2 3 4 5如果(a,b)!=(b,a)使用此映射
1 0 1 3 6 10
2 2 4 7 11 16
3 5 8 12 17 23
4 9 13 18 24 31
5 14 19 25 32 40

   1 2 3 4 5如果(a,b)==(b,a)(镜像),请使用此映射
1 0 1 2 4 6
2 1 3 5 7 10
3 2 5 8 11 14
4 4 8 11 15 19
5 6 10 14 19 24


    0 1 -1 2 -2如果需要负/正,请使用此选项
 0 0 1 2 4 6
 1 1 3 5 7 10
-1 2 5 8 11 14
 2 4 8 11 15 19
-2 6 10 14 19 24

弄清楚如何获得任意a,b的值要困难一些。


4

f(a, b) = s(a+b) + a,在哪里 s(n) = n*(n+1)/2

  • 这是一个功能-它是确定性的。
  • 它也是单射的-f为不同的(a,b)对映射不同的值。您可以使用这一事实证明了这一点:s(a+b+1)-s(a+b) = a+b+1 < a
  • 它返回的值很小-如果您打算将其用于数组索引,则该数组很好,因为数组不必很大。
  • 这是缓存友好的方法-如果两个(a,b)对彼此靠近,则f会将彼此靠近的数字映射到它们(与其他方法相比)。

我不明白您的意思是:

应该总是在整数的正数或负数上产生一个整数

如何在此论坛中写(大于),(小于)字符?


2
大于和小于字符应在内正常工作backtick escapes
TRiG 2010年

这等效于Cantor配对功能,因此不适用于负整数。
达沃·乔西波维奇

4

尽管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的通用方法稍微有效。解码也相当简单。不需要平方根,对于初学者:)


浮游生物有可能吗?
卢卡斯

4

对于正整数作为参数,并且参数顺序无关紧要:

  1. 这是一个无序的配对函数

    <x, y> = x * y + trunc((|x - y| - 1)^2 / 4) = <y, x>
    
  2. 对于x≠y,这是一个独特的无序配对函数

    <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>
    


2

这是@DoctorJ的代码根据@nawfal给定的方法扩展为无界整数的方法。它可以编码和解码。它适用于普通数组和numpy数组。

#!/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 http://stackoverflow.com/a/15391420
    x = n
    y = (x + 1) // 2
    while y < x:
        x = y
        y = (x + n // x) // 2
    return x

2

更简单的方法是什么:给定两个数字,A和B让str为串联:'A'+';' +'B'。然后让输出为hash(str)。我知道这不是一个数学答案,但是一个简单的python(具有内置的哈希函数)脚本应该可以完成这项工作。


2
但是(8,11)和(81,1)映射到相同的数字811
Leevi L

这是一个好点。您可以通过在中间添加符号来解决该问题。因此,对于(8,11)哈希字符串“ 8-11”,对于(81,1)哈希字符串“ 81-1”。因此,通常对于(A,B)哈希字符串“ AB”。(我知道这听起来很hack,但是应该可以)。
Madhav Nakar

这也是错误的,因为该任务是将两个整数映射到新的整数,而不是带有符号的字符串
Leevi L

我来自CS的观点,而不是数学的观点(有关数学解决方案,请参见上面的响应)。我将两个整数组成一个字符串,然后将其转换为整数。本质上,是的,我将两个整数映射到一个新整数。
Madhav Nakar

1

您的建议是不可能的。您将始终发生碰撞。

为了将两个对象映射到另一个单个集合,被映射的集合必须具有期望的组合数量的最小大小:

假设一个32位整数,则您有2147483647个正整数。在顺序无关紧要且重复的情况下选择其中两个,将产生2305843008139952128组合。这不太适合32位整数的集合。

但是,您可以将此映射适合61位。使用64位整数可能是最简单的。将高位字设置为较小的整数,将低位字设置为较大的整数。


1

假设您有一个32位整数,为什么不将A移到前16位的一半,而B移到另一半?

def vec_pack(vec):
    return vec[0] + vec[1] * 65536;


def vec_unpack(number):
    return [number % 65536, number // 65536];

除了这样做尽可能节省空间和计算便宜之外,一个非常酷的副作用是您可以对打包数字进行矢量数学运算。

a = vec_pack([2,4])
b = vec_pack([1,2])

print(vec_unpack(a+b)) # [3, 6] Vector addition
print(vec_unpack(a-b)) # [1, 2] Vector subtraction
print(vec_unpack(a*2)) # [4, 8] Scalar multiplication

0

让我们有两个数字B和C,将它们编码为单个数字A

A = B + C * N

哪里

B = A%N = B

C = A / N = C


2
您如何选择N以使该表示形式唯一?如果您解决了这个问题,那么这个答案与上面的答案有什么不同?
修剪

你应该补充的是N必须比B和C.更大
拉多斯拉夫扬诺夫

0

给定正整数A和B,令D = A的位数,E = B的位数。结果可以是D,0,E,0,A和B的串联。

示例:A = 300,B =12。D= 3,E = 2结果=302030012。这利用了一个事实,即以0开头的唯一数字是0,

专业版:易于编码,易于解码,可读性强,可以首先比较有效数字,无需计算即可进行比较的潜力,简单的错误检查。

缺点:结果大小是一个问题。没关系,为什么我们无论如何都要在计算机中存储无界整数。


0

如果要进行更多控制,例如为第一个数字分配X位,为第二个数字分配Y位,则可以使用以下代码:

class NumsCombiner
{

    int num_a_bits_size;
    int num_b_bits_size;

    int BitsExtract(int number, int k, int p)
    {
        return (((1 << k) - 1) & (number >> (p - 1)));
    }

public:
    NumsCombiner(int num_a_bits_size, int num_b_bits_size)
    {
        this->num_a_bits_size = num_a_bits_size;
        this->num_b_bits_size = num_b_bits_size;
    }

    int StoreAB(int num_a, int num_b)
    {
        return (num_b << num_a_bits_size) | num_a;
    }

    int GetNumA(int bnum)
    {
        return BitsExtract(bnum, num_a_bits_size, 1);
    }

    int GetNumB(int bnum)
    {
        return BitsExtract(bnum, num_b_bits_size, num_a_bits_size + 1);
    }
};

我总共使用32位。这里的想法是,例如,如果您想要第一个数字最大为10位,第二个数字最大为12位,则可以执行以下操作:

NumsCombiner nums_mapper(10/*bits for first number*/, 12/*bits for second number*/);

现在,您可以存储num_a的最大数量为2^10 - 1 = 1023,最大存储num_b值为2^12 - 1 = 4095

设置num A和num B的值:

int bnum = nums_mapper.StoreAB(10/*value for a*/, 12 /*value from b*/);

现在bnum是所有位(总共32位。您可以修改代码以使用64位)以获取num a:

int a = nums_mapper.GetNumA(bnum);

要获取num b:

int b = nums_mapper.GetNumB(bnum);

编辑: bnum可以存储在类内部。我之所以没有这样做,是因为我自己的需要,所以我分享了代码,并希望它会有所帮助。

感谢您的消息来源:https : //www.geeksforgeeks.org/extract-k-bits-given-position-number/ 用于提取位的功能,也感谢您mouviciel在本文中的回答。使用这些来源,我可以找出更高级的解决方案

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.