给定一个以 3 为底的 N 位数字,生成所有以可并行方式具有 k 位不同的以 3 为底的 N 位数字
Posted
技术标签:
【中文标题】给定一个以 3 为底的 N 位数字,生成所有以可并行方式具有 k 位不同的以 3 为底的 N 位数字【英文标题】:Given a N-digit base 3 number, generate all base 3 N-digit numbers that have k digits different in parallelizable way 【发布时间】:2011-09-09 14:19:50 【问题描述】:为了解决我的问题,使用以 3 为底的数字是很自然的。我有几个由基数 3 数字索引的表,在某些时候我需要遍历所有与给定 N 位数字以 k 位数字不同的索引。
例如,给定 120 作为 3 位基数为 3 的数字,1 位不同的数字将是: 020 220 100 110 121 122
我有一些丑陋的代码以明显的方式做到这一点,但它很慢且难以并行化。知道如何有效地做到这一点吗?
(首选语言:c++)
【问题讨论】:
【参考方案1】:这是Mathematica 中的代码。可以在Mathematica documentation center 中找到有关各个命令的文档。
digitReplacements[num3_, n_, k_] :=
Module[len, num, num3T,
len = Max[n, IntegerLength[num3]];
num = List /@ IntegerDigits[num3, 3, len];
Flatten[
ParallelTable[
num3T = num;
num3T[[ss]] = num3T[[ss]] /. 0 -> 1, 2, 1 -> 0, 2, 2 -> 0, 1;
IntegerString[FromDigits[#], 10, len] & /@ Tuples[num3T],
ss, Subsets[Range[len], k]
], 1
]
]
这段代码的剖析:
len = Max[n, IntegerLength[num3]];
num = List /@ IntegerDigits[num3, 3, len];
假设您想要包含前导零的数字,该函数将位数 (n) 作为参数。如果您不这样做,则将数字拆分为单个数字将不会生成 n 数字,如果它具有前导零。第二行将像 2110 这样的数字转换为列表 2,1,1,0。 IntegerDigits
进行拆分,List /@
将 List
映射到结果数字上,放置我们稍后需要的额外花括号。
num3T = num;
num3T[[ss]] = num3T[[ss]] /. 0 -> 1, 2, 1 -> 0, 2, 2 -> 0, 1;
其中一些子列表将被替换为一组互补的基数 3 数字(/. 是替换运算符,哪些替换参与由 ss 中的位置列表确定),以便命令 Tuples
可以使所有可能的集合。例如Tuples[1,2,3,4,5]-==> 1, 3, 4, 1, 3, 5, 2, 3, 4, 2, 3, 5
IntegerString[FromDigits[#], 10, len] & /@ Tuples[num3T],
Tuples
位于行尾。第一部分是一个纯函数,它作用于Tuples
函数的结果,用FromDigits
再次将其转换为一个数字,并使用IntegerString
处理前导零(因此,结果是一个字符串,以允许用于前导零)。
核心是基于找到所有可能的替换位置来生成这些元组的表。这是通过Subsets[Range[len], k]
行完成的,该行生成列表1,2,...,n 的所有子集,这些子集通过选择k 个数字而生成。 ParallelTable
使用生成的位置在此列表中循环,以将这些位置处的所有适用数字替换为可能对应物的列表。生成这个数字变化位置列表似乎是一种并行化问题的自然方法,因为您可以将列表的各个部分专用于各种核心。 ParallelTable
是 Mathematica 标准 Table
函数的并行计算变体,它自动处理这种并行化。
由于 ss 所采用的每组位置都会生成一个结果数字列表,因此最终结果是一个列表列表。 Flatten
将其扁平化为一个数字列表。
digitReplacements[120, 3, 1]
==> "010", "210", "100", "120", "111", "112"
digitReplacements[2012, 5, 2]
==>"10112", "11112", "20112", "21112", "12012", "12212", \
"22012", "22212", "12102", "12122", "22102", "22122", "12110", \
"12111", "22110", "22111", "00012", "00212", "01012", "01212", \
"00102", "00122", "01102", "01122", "00110", "00111", "01110", \
"01111", "02002", "02022", "02202", "02222", "02010", "02011", \
"02210", "02211", "02100", "02101", "02120", "02121"
digitReplacements[1220101012201010, 16, 6] // Length // Timing
==> 0.671, 512512
因此,我们在 0.671 秒内找到了 50 万套。如果我将ParallelTable
更改为Table
,则需要3.463秒,大约慢5倍。有点令人惊讶,因为我只有 4 个内核,而且通常并行开销会占用相当大一部分潜在的速度提升。
【讨论】:
非常感谢您的回复,看起来不错。我会尝试实现这个! @Hectorr 祝你好运!Tuples
和 Subsets
都可以方便地使用,但我认为它们在 C++ 中实现起来并不困难。替换部件可能更难以在 C++ 中镜像。【参考方案2】:
听起来你想要所有的组合只有一个digit,而不是一个bit? 1 base 3 与 2 base 3 (01 vs 10) 有 2 位不同。
如果这是您想要的,您可以通过拥有 n 个工作线程来并行化它,其中您的编号为 n 位长。为每个线程提供原始编号和要更改的数字的偏移量。该线程应返回更改该数字的两个数字。因此,在您的示例中,线程 0 将传入 ("120", 0) 并返回 ("121", "122")。然后您的主线程接收所有这些新数字并将它们添加到列表中。
【讨论】:
你是对的,我的意思是这些位可以取值 0,1 或 2。然后它是一个数字不同的组合。问题是,当您想更改 k 位数字时,这并不容易,因为您有 n!/(k!(n-k)!) 个组合,将其乘以 2^k 以获得您必须返回的数字总数。我需要一种方法来排序所有组合或类似的东西,所以我可以编写一个简单的 for 循环而不是许多嵌套的循环。以上是关于给定一个以 3 为底的 N 位数字,生成所有以可并行方式具有 k 位不同的以 3 为底的 N 位数字的主要内容,如果未能解决你的问题,请参考以下文章
SQL 服务器:如何将日期转换为以 36 为底的附加(3 位)?