以唯一且确定的方式将两个整数映射到一个整数

时间:2009-05-28 07:30:10

标签: algorithm mapping integer deterministic math

想象一下两个正整数A和B.我想将这两个整数组合成一个整数C.

没有其他整数D和E组合成C. 因此将它们与加法运算符组合不起作用。例如30 + 10 = 40 = 40 + 0 = 39 + 1 连接也不起作用。例如“31”+“2”= 312 =“3”+“12”

这种组合操作也应该是确定性的(使用相同的输入总是产生相同的结果)应该总是在整数的正侧或负侧产生一个整数。

19 个答案:

答案 0 :(得分:208)

您正在寻找双射NxN -> N映射。这些用于例如dovetailing。请查看this PDF,了解所谓的配对功能。维基百科引入了一个特定的配对功能,即Cantor pairing function

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

三条评论:

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

答案 1 :(得分:199)

考虑到它简单,快速和节省空间,

Cantor pairing function确实是最好的之一,但在Wolfram by Matthew Szudzik, here发表了更好的内容。 Cantor配对函数(相对)的限制是,如果输入是两个2N位整数,则编码结果的范围并不总是保持在N位整数的限制内。也就是说,如果我的输入是16的两个0 to 2^16 -1位整数,则可能有2^16 * (2^16 -1)个输入组合,所以显而易见Pigeonhole Principle,我们需要一个输出大小至少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的权重应用于输入并通过函数后,我将输出除以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

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

答案 2 :(得分:45)

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

在C语言中,这给出了(假设sizeof(短)= 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 :(得分:14)

这甚至可能吗?
你正在组合两个整数。它们的范围分别为-2,147,483,648到2,147,483,647,但您只能获得正数。 这使得2147483647 ^ 2 = 4,61169E + 18种组合。 由于每个组合必须是唯一的并且产生一个整数,因此您需要某种可以包含这个数字的神奇整数。

或者我的逻辑是否有缺陷?

答案 4 :(得分:8)

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

然后,结果为pqa<b,2pq a>b。如果a=b,请将其设为p^2

答案 5 :(得分:8)

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

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

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

您可以通过编码功能为5和7个字词的标记来修改此值以处理否定的xy

e.g。

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

答案 6 :(得分:4)

对于正整数作为参数以及参数顺序无关紧要:

  1. 这是unordered pairing function

    <x, y> = x * y + trunc((|x - y| - 1)^2 / 4) = <y, x>
    
  2. 对于x≠y,这是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>
    

答案 7 :(得分: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将数字映射到彼此接近的数字(与其他方法相比)。

我不明白你的意思:

  

应始终生成一个整数   无论是积极的还是消极的   整数的一面

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

答案 8 :(得分:4)

构建映射并不困难:

   1  2  3  4  5  use this mapping if (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 use this mapping if (a,b) == (b,a) (mirror)
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 use this if you need negative/positive
 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的值有点困难。

答案 9 :(得分:3)

虽然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更通用的方法更有效。解码起来也相当简单;首先要求没有平方根:)

答案 10 :(得分:2)

选中此项:http://en.wikipedia.org/wiki/Pigeonhole_principle。如果A,B和C属于同一类型,则无法完成。如果A和B是16位整数,而C是32位,那么你可以简单地使用移位。

散列算法的本质是它们不能为每个不同的输入提供唯一的散列。

答案 11 :(得分:2)

这是@DoctorJ的代码扩展到无限整数,基于@nawfal给出的方法。它可以编码和解码。它适用于普通数组和numpy数组。

fileChooser.getCurrentDirectory().getPath();

答案 12 :(得分:1)

你的建议是不可能的。你总会发生碰撞。

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

假设一个32位整数,则有2147483647个正整数。选择其中两个顺序无关紧要并重复产生2305843008139952128组合。这不适合32位整数集。

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

答案 13 :(得分:1)

如何更简单:给出两个数字,A和B让str成为连接:&#39; A&#39; +&#39;;&#39; +&#39; B&#39;。然后让输出为hash(str)。我知道这不是一个数学答案,但是一个简单的python(它有一个内置的哈希函数)脚本应该可以胜任。

答案 14 :(得分:0)

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

A = B + C * N

,其中

B = A%N = B

C = A / N = C

答案 15 :(得分:0)

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

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

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

缺点:结果大小是一个问题。但这没关系,为什么我们仍然将无界整数存储在计算机中。

答案 16 :(得分:0)

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

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


def vec_unpack(number):
    return [number % 65536, int(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] Vector multiplication

经历了这两点

答案 17 :(得分: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的最低值。

设置数字A和数字B的值:

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

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

int a = nums_mapper.GetNumA(bnum);

要获取数字b:

int b = nums_mapper.GetNumB(bnum);

编辑: bnum可以存储在类中。我没有这么做是因为我自己的需要 我分享了代码,希望对您有所帮助。

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

答案 18 :(得分:0)

我们可以将两个数字在O(1)空间和O(N)时间中编码为一个。 假设您要将0-9范围内的数字编码为1,例如。 5和6。如何做?简单

  5*10 + 6 = 56. 
   
    5 can be obtained by doing 56/10 
    6 can be obtained by doing 56%10.

即使对于两位整数,也就是56和45,即56 * 100 + 45 =5645。我们可以通过执行5645/100和5645%100再次获得单个数字

但是对于大小为n的数组,例如。 a = {4,0,2,1,3},假设我们要编码3和4,所以:

 3 * 5 + 4 = 19               OR         3 + 5 * 4 = 23
 3 :- 19 / 5 = 3                         3 :- 23 % 5 = 3
 4 :- 19 % 5 = 4                         4 :- 23 / 5 = 4

将其概括化后,我们得到

    x * n + y     OR       x + n * y

但是我们还需要照顾到我们改变的价值。所以最终以

    (x%n)*n + y  OR x + n*(y%n)

您可以通过除以并找到所得数字的mod来单独获取每个数字。