【问题标题】:Given a N-digit base 3 number, generate all base 3 N-digit numbers that have k digits different in parallelizable way给定一个以 3 为底的 N 位数字,生成所有以可并行方式具有 k 位不同的以 3 为底的 N 位数字
【发布时间】:2011-09-09 14:19:50
【问题描述】:

为了解决我的问题,使用以 3 为底的数字是很自然的。我有几个由基数 3 数字索引的表,在某些时候我需要遍历所有与给定 N 位数字以 k 位数字不同的索引。

例如,给定 120 作为 3 位基数为 3 的数字,1 位不同的数字将是: 020 220 100 110 121 122

我有一些丑陋的代码以明显的方式做到这一点,但它很慢且难以并行化。知道如何有效地做到这一点吗?

(首选语言:c++)

【问题讨论】:

    标签: parallel-processing combinatorics


    【解决方案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 循环而不是许多嵌套的循环。
    猜你喜欢
    • 2023-03-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-03-27
    • 1970-01-01
    • 1970-01-01
    • 2014-03-13
    • 1970-01-01
    相关资源
    最近更新 更多