为给定的基数和位数生成所有可能的排列

Posted

技术标签:

【中文标题】为给定的基数和位数生成所有可能的排列【英文标题】:Generating all possible permutations for a given base and number of digits 【发布时间】:2012-05-03 21:05:53 【问题描述】:

我确信这很简单,但我很难找到一种方法来做到这一点。本质上,如果我有一个包含 P 列和 V^P 行的数组,我该如何填写所有组合,也就是说,基本上,所有可能的数字都在 P 位的基数 V 中。例如,对于 P=3 和 V=2:

000
001
010
011
100
101
110
111

请记住,这是一个二维数组,而不是整数数组。

对于 P=4 和 V=3。

0000
0001
0002
0010
0011
0012
....

生成了这个数组后,我想要开发的其余工作就变得微不足道了。因此,非常感谢您提供一些有关如何执行此操作的代码/提示。谢谢。

【问题讨论】:

【参考方案1】:

以 P=3 和 V=2 为例,在第一列中,您需要以下数字序列:

0, 0, 0, 0, 1, 1, 1, 1

所以你基本上需要四个 0,然后是四个 1。

在第二列中你需要:

0, 0, 1, 1, 0, 0, 1, 1

所以你想要两个 0 后跟两个 1,然后再跟一个。

一般情况下,在第n列,每个数字需要V^(P-n),重复V^(n-1)次。

P=3 和 V=2 时的示例:

第 1 列:我们需要每个数字的 V^(P-n) = 2^(3-1) = 4,重复 V^(n-1) = 2^0 = 1 次:

[0, 0, 0, 0, 1, 1, 1, 1]

第2列:我们需要每个数字的V^(P-n) = 2^(3-2) = 2,重复V^(n-1) = 2^1 = 2次:

[0, 0, 1, 1], [0, 0, 1, 1]

第 3 列:我们需要每个数字的 V^(P-n) = 2^(3-3) = 1,重复 V^(n-1) = 2^2 = 4 次:

[0, 1], [0, 1], [0, 1], [0, 1]

生成此序列的一些 Python 代码:

def sequence(v, p, column):
    subsequence = []
    for i in range(v):
        subsequence += [i] * v**(p - column)
    return subsequence * v**(column - 1)

【讨论】:

【参考方案2】:

基本上这是制作一个 vp 数字列表,从 0 到基数 v 中的最大数字宽度 pnumpy.base_repr 可用于在 Python 中执行此操作:

from numpy import base_repr

def base_of_size(base, size):
    for i in range(base ** size):
        yield base_repr(i, base).rjust(size, "0")

此外,itertools.product(range(v), repeat=p) 是另一个可以完成这项工作的 Python 内置函数(结果证明效率最高——请参阅下面的基准测试)。

这是从 numpy.base_repr 转换为 C# 的算法(Convert.ToString() 对碱基非常有选择性):

using System;
using System.Collections.Generic;

class Converter

    public static IEnumerable<string> BaseOfSize(int baseN, int size) 
    
        for (int i = 0; i < Math.Pow(baseN, size); i++) 
        
              yield return BaseRepr(i, baseN).PadLeft(size, '0');
        
    

    public static string BaseRepr(int n, int baseN)
    
        string digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
        var res = new List<char>();

        for (int num = Math.Abs(n); num > 0; num /= baseN) 
        
            res.Add(digits[num%baseN]);  
        

        if (n < 0) res.Add('-');

        res.Reverse();
        return string.Join("", res);
    

    public static void Main(string[] args) 
    
        foreach (var n in BaseOfSize(2, 3)) 
        
            Console.WriteLine(n);
        

        Console.WriteLine();

        foreach (var n in BaseOfSize(3, 4)) 
        
            Console.WriteLine(n);
        
    

输出:

000
001
010
011
100
101
110
111

0000
0001
0002
0010
0011
0012
0020
 ...
2220
2221
2222

虽然 numpy 版本使用简单且可迭代,但速度也很慢。使用递归 DFS 方法意味着我们不必从头开始计算每个数字,而是可以简单地增加前一个数字,直到我们到达新的叶子。这些版本不使用生成器,但很容易调整:

Python:

def base_of_size(base, size):
    def recurse(res, row, i=0):
        if i >= size:
            res.append(row[:])
        else:
            for j in range(base):
                row[i] = j
                recurse(res, row, i + 1)

        return res

    return recurse([], [None] * size)

C#:

using System;
using System.Collections.Generic;

class Converter

    public static List<List<int>> BaseOfSize(int v, int p) 
    
        var res = new List<List<int>>();
        BaseOfSize(v, p, 0, new List<int>(new int[p]), res);
        return res;
    

    private static void BaseOfSize(int v, int p, int i, List<int> row, List<List<int>> res)
    
        if (i >= p) 
        
            res.Add(new List<int>(row));
        
        else 
        
            for (int j = 0; j < v; j++) 
             
                row[i] = j;
                BaseOfSize(v, p, i + 1, row, res);
            
        
    

快速基准测试(使用生成器):

from itertools import product
from time import time
from numpy import base_repr

def base_of_size(base, size):
    def recurse(res, row, i=0):
        if i >= size:
            yield row[:]
        else:
            for j in range(base):
                row[i] = j
                yield from recurse(res, row, i + 1)

        return res

    yield from recurse([], [None] * size)

def base_of_size2(base, size):
    for i in range(base ** size):
        yield base_repr(i, base).rjust(size, "0")

if __name__ == "__main__":
    start = time()
    list(base_of_size(10, 6))
    end = time()
    print("dfs:", end - start)
    start = time()
    list(base_of_size2(10, 6))
    end = time()
    print("base_repr:", end - start)
    start = time()
    list(product(range(10), repeat=6))
    end = time()
    print("product:", end - start)

输出:

dfs: 4.616123676300049
base_repr: 9.795292377471924
product: 0.5925478935241699

itertools.product 以远射获胜。

【讨论】:

这可行,但如果长度太大,我会得到堆栈溢出异常。有没有办法不用递归来实现? 好的,添加了重写。这个版本如何为您工作?它更慢但迭代。还在思考这个问题。 是的,我看的越多,我就越不明白递归版本上的堆栈是如何溢出的。深度是p,它是指数,所以即使只有 2^30 也会给你 1073741824 个数字的结果,而 30 远不及堆栈限制。我不确定我是否可以在没有大量字符串连接或数组复制的情况下迭代地编写堆栈版本,也没有必要。如果是内存问题,也许生成器版本可以工作。 别担心。我搞砸了并传入了一个对 p 来说太大的值。它工作正常。【参考方案3】:

如果每个“数字”中有不同数量的选项,则可以使用此代码。

也许在某些优化工具中存在执行此操作的算法,因为它可能对蛮力方法有用。下面的代码添加了一个显示“最大数字的值”的列,可以忽略:

import numpy as np
val=np.arange(15)
options=[2,2,3]
print(val)
print(options)
opt = options + [1] # Assumes options to be a list
opt_cp = np.flip(np.cumprod(np.flip(np.array(opt))))
ret = np.floor_divide(val[:,np.newaxis], opt_cp[np.newaxis,:])
ret[:,1:] = np.remainder(ret[:,1:], np.array(opt[:-1])[np.newaxis,:])
inds = ret[:,1:]
print(inds)

【讨论】:

【参考方案4】:

您还可以使用 numpy 的 N 维网格网格函数。

例如

np.mgrid[0:2,0:2,0:2].reshape((3, 8)).T

array([[0, 0, 0],
       [0, 0, 1],
       [0, 1, 0],
       [0, 1, 1],
       [1, 0, 0],
       [1, 0, 1],
       [1, 1, 0],
       [1, 1, 1]])

np.stack(np.meshgrid(range(2), range(2), range(2), indexing='ij')).reshape(3, -1).T

或者一般来说任何PV

np.mgrid[[slice(0, V)]*P].reshape((P, -1)).T

np.stack(np.meshgrid(*[range(V)]*P, indexing='ij')).reshape((P, -1)).T

一定有更明显的方法,但我想不出什么。

【讨论】:

以上是关于为给定的基数和位数生成所有可能的排列的主要内容,如果未能解决你的问题,请参考以下文章

算法基数排序

递归求解:用0到9生成十位数的所有排列组合

递归求解:用0到9生成十位数的所有排列组合

20191209-八大排序之基数排序

2023-03-02:给定一个数组arr,长度为n, 任意相邻的两个数里面至少要有一个被选出来,组成子序列,才是合法的! 求所有可能的合法子序列中,最大中位数是多少? 中位数的定义为上中位数, [1,

从不同的基数转换后查找位数的方法