【问题标题】:Algorithm to reversibly map one range of integers into another range of integers?将一个整数范围可逆地映射到另一个整数范围的算法?
【发布时间】:2017-08-25 16:56:31
【问题描述】:

什么是一种节省时间和空间效率的算法,用于将离散的整数范围(比如区间 I1 [A..B],其中 B>=A)线性映射到另一个更大的整数范围(比如区间I2 [C..D] 其中 D>=C)?

这里简单表示,第二个范围I2的大小被限制为大于或等于第一个范围I1,即D-C >= B-A。

因此,I1 中的每个整数都映射到 I2 中包含的一个或多个对应整数的集合(因为 I2 的大小大于 I1 的大小)。相反,I2 中的每个整数都映射回 I1 中唯一的对应整数。

所以有两种期望的算法,它们在两个提供的整数区间的域中运行:区间 I1 [A..B] 和区间 I2 [C..D](其中 B>=A 和 D>=C 并且D-C >= B-A)

  1. 算法 A1:给定 I1 中包含的整数 X,计算 I2 中相应的线性映射整数集。将此表示为 A1(I1,I2,X) = [M..N],其中 [M..N] 是 I2 的子区间。

  2. 算法 A2:给定区间 I2 中包含的整数 Y,计算 I1 中的单个对应整数。将此表示为 A2(I1,I2,Y) = X。

算法 A2 必须是 A1 的对称逆。即对于 I1 中的所有 X:A1(I1,I2,X) = [M..N],那么对于 [M..N] 中的所有 Y:A2(I1,I2,Y) = X

显然,由于此问题仅限于整数,因此映射可能不是完全线性的。例如,如果 I1 包含三个整数 [1..3],而 I2 包含四个整数 [4..7],则 I1 中的两个整数将在 I2 中具有单个映射,并且 I1 中的第三个整数将具有I2 中的两个映射。 I1 中具有两个映射的特定整数无关紧要。重要的是,算法 A1 选择具有两个映射(Y 和 Y+1)的整数 (X),然后算法 A2 必须将这两个相同的 Y 值映射回 I1 中的原始 X。

算法 A1 和 A2 应尽可能创建从 I1 到 I2 的最线性映射。换句话说,如果区间 I1 的大小为 J,而区间 I2 的大小为 K(记得 K>=J),那么 I1 的每个整数要么具有 TRUNC(K/J) 映射,要么具有 TRUNC(K/J)+1 映射进入 I2。

算法应该占用常量空间,因此由一组可能使用截断整数除法和模运算以及其他基本数学函数的代数方程组成。换句话说,算法不能为需要空间来将每个映射存储在表中的映射创建表,因为整数间隔的大小可以达到 2^64。

编辑:例如,假设区间 I1 = [0..2] 和区间 I2 = [0..4]。一种正确的解决方案可能是:

Algorithm A1                    Algorithm A2
X=0, Y=[0..1]                   Y=0, X=0
X=1, Y=[2..3]                   Y=1, X=0
X=2, Y=[4]                      Y=2, X=1
                                Y=3, X=1
                                Y=4, X=2

另一个同样正确的解决方案是:

Algorithm A1                    Algorithm A2
X=0, Y=0                        Y=0, X=0
X=1, Y=[1..2]                   Y=1, X=1
X=2, Y=[3..4]                   Y=2, X=1
                                Y=3, X=2
                                Y=4, X=2

一个不正确的解决方案是:

Algorithm A1                    Algorithm A2
X=0, Y=[0..1]                   Y=0, X=0
X=1, Y=[2..3]                   Y=1, X=1
X=2, Y=[4]                      Y=2, X=1
                                Y=3, X=2
                                Y=4, X=2

上述解决方案不正确。尽管 A1 确实将 [0..2] 线性映射到 [0..4],并且 A2 确实将 [0..4] 线性映射回 [0..2],但问题是 A2 不是 A1 的逆.例如,对于 X=0,A1(0) 的值之一是 Y=1,但 A2(1) 给出 X=1(而不是原始值 0)。

【问题讨论】:

  • Y = C + (X - A) * (D - C) / (B - A), X = A + (Y - C) * (B - A) / (D - C) 有什么问题?
  • @Mbo 是的,这很容易,但它只为 Y 提供一个整数值,其中 Y 确实需要是一个范围,因为每个 X 值都可以映射到多个 Y 值。这在问题中有详细描述。
  • @deltamind106 区间 Y 由 C + (X - A) * (D - C) / (B - A) .. C + (X + 1 - A) * (D - C) / (B - A) - 1 组成。您可能正在寻找更好的答案,但我想就是这样。早点想出这样的答案,却不得不离开。
  • 我必须澄清一下:映射的整数是否必须是一个连续的区间,或者它们是否可以分布在整个 A2 的范围内。
  • @Prune 这是一个我没有想到的有趣想法。最初的想法是 A2 会产生一个连续的间隔,但分布可能是可行的....我不确定(我们试图解决的问题是密码学领域)。

标签: algorithm math integer discrete-mathematics


【解决方案1】:

您可以使用multiplicative inverse 和偏移量轻松完成此操作。首先减去A。然后进行乘法逆计算,并通过添加C来转换结果。

要反转它,减去 D,进行乘法逆计算的逆运算,然后加上 A。

这很好用。我以前用过,效果很好。

【讨论】:

  • 如果我没记错的话,我认为计算乘法逆的算法或乘法逆的逆不是多项式时间?我认为其中一个是一个难题,当涉及到 2^64 数量级的大量数字时。是对的吗?也许我错了……
  • @deltamind106:计算一个数字的乘法逆元是一项看起来是 O(log n) 的一次性工作。在这种情况下,每次获得新的 A-B 范围时都需要计算它。之后,转换任何数字或反转转换都是 O(1) 操作。有关示例,请参阅我的博客文章 blog.mischel.com/2017/06/20/how-to-generate-random-looking-keys。它不会转换到新范围,但这只是加法(或反转时的减法)。
  • 好的,但您似乎仍然必须在计算后将计算的映射存储在表中。问题是我们将使用的 A..B 范围很大,例如 2^32
  • @deltamind106:重点是除了计算出的任何范围的multInv 值外,您不需要存储任何内容。当您获得一个新的 A..B 范围时,您计算 multInv 值,并使用它来为该范围进行映射。
  • @deltamind106:我已经阅读了博客。这至少与我今天早些时候回应您的评论时给您的答案一样好。在我看来,这很容易被选为“公认的答案”。
【解决方案2】:

我建议不要使用间隔和截断,而是使用模块化映射:只需将 i1 的元素按数字顺序映射到 i2 上,并根据需要进行换行。这是 Python 中的代码(为了清晰和通用性,有一些过于简单和冗余的分配),以及范围 [2:5] 和 [4:13] 的示例

代码:

def alg_1(x, interval_1, interval_2):
# Map X, an element of interval 1,
#   to a list of elements in interval 2.

    lo_1 = interval_1[0]
    hi_1 = interval_1[1]
    lo_2 = interval_2[0]
    hi_2 = interval_2[1]
    range_1 = hi_1 - lo_1 + 1
    range_2 = hi_2 - lo_2 + 1
    pos_1 = x - lo_1

    base_val = pos_1 + lo_2
    y = range(base_val, hi_2+1, range_1)


    return y

def alg_2(y, interval_1, interval_2):
# Map Y, an element of interval 2,
#   to its corresponding element in interval1.

    lo_1 = interval_1[0]
    hi_1 = interval_1[1]
    lo_2 = interval_2[0]
    hi_2 = interval_2[1]
    range_1 = hi_1 - lo_1 + 1
    range_2 = hi_2 - lo_2 + 1
    pos_2 = y - lo_2

    x = lo_1 + pos_2 % range_1

    return x


i1 = (2, 5)
i2 = (4, 13)

print "X  Y"

for val in range(i1[0], i1[1]+1):
    print val, alg_1(val, i1, i2)

print ""
print "Y  X"

for val in range(i2[0], i2[1]+1):
    print val, alg_2(val, i1, i2)

输出:

X  Y
2 [4, 8, 12]
3 [5, 9, 13]
4 [6, 10]
5 [7, 11]


Y  X
4 2
5 3
6 4
7 5
8 2
9 3
10 4
11 5
12 2
13 3

问题 对于 X=>Y 映射,此方法返回映射值列表。此列表的大小为|i2| / |i1|。这可以接受吗?当这个比率上升到大约 2^25 时,它会变慢。

如果你愿意,你可以返回一个 xrange 而不是一个范围;那是序列的生成器,而不是序列本身。

【讨论】:

  • 我是 C++ 和 Java 开发人员,不懂 python,所以如果这是一个愚蠢的问题,请原谅我的无知。但看起来您的 alg_1 实现会迭代以找到要添加到结果集中的正确值。这是准确的吗?如果是这样,那么当使用大小为 2^32 到 2^64 的大间隔时,它将非常低效,而这正是我们所需要的。由于我不懂 python,你能评论一下这个问题吗?
  • 是的;对于 20 年代中期的指数来说,它变得太慢了;对于大间隔中的任何内容,我们需要更快的循环或不同的映射。我会努力的。
  • 我改变了 X=>Y 映射的计算。现在它更快了。将小 i1 映射到大 i2 仍然很慢;当大小比率达到大约 2^25 时,减速变得明显。在 2^28 时,将十个 X 值映射到它们的 Y 集大约需要 48 秒。
  • 看到这就是这种方法的问题,我们确实需要一个特定 X 的每个可能的 Y 映射的列表。原因是我们要随机选择一个 Y 映射。这就是我最初想要一个映射范围的原因,从那时起,很容易从该范围中随机选择一个整数。当映射是一组不同的值时,必须对它们进行枚举,以便您可以随机选择一个。
猜你喜欢
  • 2020-03-03
  • 1970-01-01
  • 2023-03-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多