给定一个以 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 祝你好运! TuplesSubsets 都可以方便地使用,但我认为它们在 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 位数字的主要内容,如果未能解决你的问题,请参考以下文章

NumPy:以 n 为底的对数

查找堆栈中最长的字符序列

SQL 服务器:如何将日期转换为以 36 为底的附加(3 位)?

实现以(-2)代替2为底的二进制表示

用户输入给出“ValueError:int() 以 10 为底的无效文字:”

添加位 mod n 的最简单方法?