假设您有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}},其中95918155193915183
和155
是一些分隔符,例如基于前面的输入或其他技巧的内插器值。一种更简单的方法就是说“跟随5
的三个零000
之后的任何东西表示它是一个新的整数。因此410001414
基本上是一个分隔符。但这特别丑陋而脆弱。也许它可能会变得更棘手,更有效,例如“如果值是奇数,然后是其自身的3的倍数,那么它就是一个分隔符”之类的东西。但是我可以看到它也有脆弱的情况。但是,基本上,给定一组整数000
(由整数字符组成的字符串),如何将其转换为单个整数(或单个整数字符的字符串),然后将其转换回原始集合整数n
。
答案 0 :(得分:1)
当然,有很多方法可以做到这一点。
首先,只需要具有将两个值合并为一个的可逆函数。 (要使其可逆,必须有另一个函数来获取输出值并重新创建两个输入值。)
让我们调用结合了两个值combine
和反向函数separate
的函数。然后我们有:
separate(combine(a, b)) == [a, b]
对于任何值a
和b
。这意味着combine(a, b) == combine(c, d)
仅当a == c
和b == 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 ...
(在此答案的早期版本中,我颠倒了a
和b
,但此版本似乎具有更直观的输出值。)
请注意,顶行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
仅丢弃小数部分,因此结果是对角线索引。
现在我们只需要恢复a
和b
,这很简单:
a = m - combine(0, s)
b = s - a
因此,我们现在有了combine
和separate
的定义:
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为底)读取结果。
这两个解决方案都不具有每个非负整数都是某个矢量的编码的特性。 (第二个几乎具有该属性,这对于找出哪些整数是不可能的编码,然后修复编码算法以避免该问题是很有用的。)