如果您可以组合3个以上任意大小的整数,并且仍然能够对其进行解构

时间:2019-02-09 06:05:29

标签: parsing hash integer

假设您有3个整数:

13105
705016
13

我想知道您是否可以通过任何方式将它们组合成一个整数 ,这样您仍然可以回到原始的3个整数。

var startingSet = [ 13105, 705016, 13 ]
var combined = combineIntoOneInteger(startingSet)
// 15158958589285958925895292589 perhaps, I have no idea.
var originalIntegers = deconstructInteger(combined, 3) 
// [ 13105, 705016, 13 ]

function combineIntoOneInteger(integers) {
  // some sort of hashing-like function...
}

function deconstructInteger(integer, arraySize) {
  // perhaps pass it some other parameters 
  // like how many to deconstruct to, or other params.
}

从技术上讲,它不必是“整数”。它只是只使用整数字符的字符串,尽管也许我可能想使用十六进制字符。但是我用整数来问,因为在下面我确实有一个有界大小的整数,将用于构造组合对象。

其他一些注释....

  • 组合的值应该是唯一的,因此,无论您组合的是什么值,您总会得到不同的结果。也就是说,绝对没有冲突。或者,如果不可能的话,也许可以解释原因和可能的解决方法。
  • 包含所有可能输出的数学“集合”可以由不同数量的组件组成。也就是说,您可能具有包含[ 100, 200, 300, 400 ]的输出/组合集,但输入集是这4个数组:[ [ 1, 2, 3 ], [ 5 ], [ 91010, 132 ], [ 500, 600, 700 ] ]。也就是说,输入数组的长度可以完全不同,整数大小可以完全不同。
  • 更一般地实现此目的的一种方法是仅使用“分隔符”字符,这使得超级简单。就像13105:705016:13。但这是作弊,我希望它仅使用整数集(或十六进制集或其他任意集,但在这种情况下仅是整数集或十六进制)中的字符。
  • 可能的另一种想法是通过做一些散列或置换柔术柔和地隐藏其中的分隔符,从而使[ 13105, 705016, 13 ]变成一些看起来像{ {1}},其中95918155193915183155是一些分隔符,例如基于前面的输入或其他技巧的内插器值。一种更简单的方法就是说“跟随5的三个零000之后的任何东西表示它是一个新的整数。因此410001414基本上是一个分隔符。但这特别丑陋而脆弱。也许它可能会变得更棘手,更有效,例如“如果值是奇数,然后是其自身的3的倍数,那么它就是一个分隔符”之类的东西。但是我可以看到它也有脆弱的情况。

但是,基本上,给定一组整数000(由整数字符组成的字符串),如何将其转换为单个整数(或单个整数字符的字符串),然后将其转换回原始集合整数n

1 个答案:

答案 0 :(得分:1)

当然,有很多方法可以做到这一点。

首先,只需要具有将两个值合并为一个的可逆函数。 (要使其可逆,必须有另一个函数来获取输出值并重新创建两个输入值。)

让我们调用结合了两个值combine和反向函数separate的函数。然后我们有:

separate(combine(a, b)) == [a, b]

对于任何值ab。这意味着combine(a, b) == combine(c, d) 仅当a == cb == d时才为true;换句话说,每对输入都会产生不同的输出。

编码任意向量

有了这个功能,我们就可以编码任意长度的输入向量。最简单的情况是我们事先知道向量的长度是多少。例如,我们可以定义:

combine3 = (a, b, c) => combine(combine(a, b), c)
combine4 = (a, b, c, d) => combine(combine(combine(a, b), c), d)

,依此类推。要反转该计算,我们只需要重复调​​用separate正确的次数,每次都保留第二个返回值。例如,如果我们之前已经计算过:

m = combine4(a, b, c, d)

我们可以按以下方式获取四个输入值:

c3, d = separate(m)
c2, c = separate(c3)
a, b  = separate(c2)

但是您的问题要求一种组合任意数量的值的方法。为此,我们只需要做一个最终的combine,就可以将值的数量混合在一起。这样就可以取回原始向量:首先,调用separate以取回值计数,然后调用足够多的时间来提取每个连续的输入值。

combine_n = v => combine(v.reduce(combine), v.length)
function separate_n(m) {
  let [r, n] = separate(m)
  let a = Array(n)
  for (let i = n - 1; i > 0; --i) [r, a[i]] = separate(r);
  a[0] = r;
  return a;
}

请注意,上述两个函数在空向量上不起作用,该向量应编码为0。为此,请为这种情况添加正确的检查。还要注意此答案底部的警告,关于整数溢出。


一个简单的合并功能:对角化

完成后,让我们看一下如何实现combine。实际上有很多解决方案,但是一个非常简单的解决方案是使用对角线化功能:

 diag(a, b) = (a + b)(a + b + 1)
              ------------------ + a
                        2

这基本上是通过跟踪连续的对角线来分配无限方格中的位置:

        <-- b -->
    0  1  3  6 10 15 21 ...
 ^  2  4  7 11 16 22 ...
 |  5  8 12 17 23 ...
 a  9 13 18 24 ...
 | 14 19 25 ...
 v 20 26 ...
   27 ...

(在此答案的早期版本中,我颠倒了ab,但此版本似乎具有更直观的输出值。)

请注意,顶行a == 0恰好是triangular numbers,这并不奇怪,因为已经枚举的位置是正方形的左上三角形。

要反转变换,我们首先求解定义三角数m = s(s + 1)/2的方程,该方程与

相同
0 = s² + s - 2m

可以使用标准solution找到quadratic formula的人,结果:

s = floor((-1 + sqrt(1 + 8 * m)) / 2)

({s是原始的a+b;即对角线的索引。)

我应该解释对floor的调用,该调用一直潜入其中。 s恰好是正方形顶行的整数,其中a为0。但是,当然,a通常不是0,而m通常会比我们要寻找的三角数大一点,因此,当我们求解s时,我们将得到一些小数值。 Floor仅丢弃小数部分,因此结果是对角线索引。

现在我们只需要恢复ab,这很简单:

a = m - combine(0, s)
b = s - a

因此,我们现在有了combineseparate的定义:

let combine = (a, b) => (a + b) * (a + b + 1) / 2 + a

function separate(m) {
  let s = Math.floor((-1 + Math.sqrt(1 + 8 * m)) / 2);
  let a = m - combine(0, s);
  let b = s - a;
  return [a, b];
}

此特定编码的一个很酷的功能是,每个非负整数都对应一个不同的向量。许多其他编码方案不具有此属性。 combine_n的可能返回值是该组非负整数的子集。


示例编码

作为参考,以下是前30个编码值及其代表的向量:

> for (let i = 1; i <= 30; ++i) console.log(i, separate_n(i));
 1 [ 0 ]
 2 [ 1 ]
 3 [ 0, 0 ]
 4 [ 1 ]
 5 [ 2 ]
 6 [ 0, 0, 0 ]
 7 [ 0, 1 ]
 8 [ 2 ]
 9 [ 3 ]
10 [ 0, 0, 0, 0 ]
11 [ 0, 0, 1 ]
12 [ 1, 0 ]
13 [ 3 ]
14 [ 4 ]
15 [ 0, 0, 0, 0, 0 ]
16 [ 0, 0, 0, 1 ]
17 [ 0, 1, 0 ]
18 [ 0, 2 ]
19 [ 4 ]
20 [ 5 ]
21 [ 0, 0, 0, 0, 0, 0 ]
22 [ 0, 0, 0, 0, 1 ]
23 [ 0, 0, 1, 0 ]
24 [ 0, 0, 2 ]
25 [ 1, 1 ]
26 [ 5 ]
27 [ 6 ]
28 [ 0, 0, 0, 0, 0, 0, 0 ]
29 [ 0, 0, 0, 0, 0, 1 ]
30 [ 0, 0, 0, 1, 0 ]

警告!

观察到所有未编码的值都非常小。编码后的值的大小类似于所有输入值的并置,因此它确实迅速增长。您必须小心,不要超出Javascript在精确整数计算上的限制。一旦编码值超过此限制(2 53 ),将不再可能反转编码。如果输入向量较长和/或编码值较大,则需要找到某种bignum支持才能进行精确的整数计算。


替代合并功能

combine的另一种可能的实现方式是:

let combine = (a, b) => 2**a * 3**b

实际上,使用质数的幂,我们可以省去combine_n序列,而直接产生组合:

combine(a, b, c, d, e,...) = 2a 3b 5c 7d 11e ...

(这假设编码值严格为正;如果它们可以为0,我们将无法知道序列的长度,因为编码值无法区分向量和具有0的相同向量但这不是一个大问题,因为如果我们需要处理0,我们只需在所有使用的指数中加一个即可。

combine(a, b, c, d, e,...) = 2a+1 3b+1 5c+1 7d+1 11e+1 ...

这当然是正确的,并且从理论上讲非常优雅。这是在理论CS教科书中可以找到的解决方案,因为证明唯一性和可逆性要容易得多。但是,在现实世界中,这实际上是不实际的。反转组合取决于找到编码值的素因,并且编码值确实很大,远远超出了易于表示的数字范围。

另一种可能正是您在问题中提到的一种可能性:只需在连续值之间放置一个分隔符即可。一种简单的方法是重写要在基数9(或基数15)中编码的值,然后递增所有数字值,以使数字0不出现在任何编码值中。然后,我们可以在编码值之间添加0,然后以10为底(或以16为底)读取结果。

这两个解决方案都不具有每个非负整数都是某个矢量的编码的特性。 (第二个几乎具有该属性,这对于找出哪些整数是不可能的编码,然后修复编码算法以避免该问题是很有用的。)