整数和数组排列之间的惰性映射,而不生成所有排列

Posted

技术标签:

【中文标题】整数和数组排列之间的惰性映射,而不生成所有排列【英文标题】:lazy mapping between integers and arrays permutations without generating all the permutations 【发布时间】:2022-01-22 21:59:07 【问题描述】:

我想在python中生成一组整数数组和整数值之间的映射,例如n=2(数组大小)和m=2(数组项范围),我想要:


   0: [0, 0, 0],
   1: [0, 0, 1],
   2: [0, 1, 1],
   3: [1, 0, 0],
   ...
   7: [1, 1, 1]


一种方法是使用itertools.product 并执行以下操作:

from itertools import product
import numpy as np
n = 2
m = 3
a = list(product(np.arange(n), repeat=3))
a = list(map(lambda l: list(l), a))
b = dict(zip(np.arange(len(a)), a))

b 将是:

0: [0, 0, 0], 1: [0, 0, 1], 2: [0, 1, 0], 3: [0, 1, 1], 4: [1, 0, 0], 5: [1, 0, 1], 6: [1, 1, 0], 7: [1, 1, 1]

我可以有上面提到的映射,例如

>>> b[1]
[0, 0, 1]

但是,在我的情况下,n=8m=16 以及存储这样的字典(8**16 条目!)将占用超过 100 Gb。我想知道是否有一种懒惰的方式来生成 b 的每个索引,每次我想访问它而不生成整个字典?

【问题讨论】:

你的第一句话应该是n=3吗? “但是,在我的情况下,n=8 和 m=16” 这是您真正感兴趣的唯一情况吗?或者您对nm 的一般值感兴趣? 【参考方案1】:

您可以创建一个函数而不是完整的地图。您的输出实际上是大小为 m 的数组,表示输入 a 在 n-th 表示法中的值。如果要制作地图,如果键不存在,只需将函数的结果保存在地图中即可。

def f(a, n, m):
    res = []
    while a != 0:
        res = [a % n] + res
        a //= n
        m -= 1
    while m != 0:
        res = [0] + res
        m -= 1
    return res

【讨论】:

【参考方案2】:

您可以通过将连续的x//n**(i-1)%n 用于在 m-1 和 0 之间变化的 i 来生成第 x 个元素。

n = 3
m = 4

x = 6
[x//n**(i-1)%n for i in range(m, 0, -1)]

输出:[0, 0, 2, 0]

作为函数
def f(x, n, m):
    return [x//n**(i-1)%n for i in range(m, 0, -1)]

f(1, n=2, m=3)
# [0, 0, 1]

f(1234567890, n=8, m=16)
# [0, 0, 0, 0, 0, 1, 1, 1, 4, 5, 4, 0, 1, 3, 2, 2]

【讨论】:

【参考方案3】:

在您的示例中,您更像是映射产品而不是排列。

给出的具体示例可以转换为一个函数,该函数将为您提供整数位(无需存储任何内容):

def bits(n,w=16): return [*map(int,f'n:0wb')]

bits(5,3)  # [1, 0, 1]
bits(13,8) # [0, 0, 0, 0, 1, 1, 0, 1]

对于更通用的“产品”虚拟列表,您可以创建一个函数从序列列表中获取第 N 个产品,该序列将涵盖您的示例等:

def prodAtIndex(i,L):
    result = []                     # product fed in reversed order           
    for v in reversed(L):           # from least significant to most
        i,p = divmod(i,len(v))      # get digit in current 'base'
        result.insert(0,v[p])       # build product
    return result if i==0 else None # i will end at zero if in range

prodAtIndex(16,[(0,1,2)]*3)      # [1,2,1]
prodAtIndex(13,[(0,1)]*8)        # [0, 0, 0, 0, 1, 1, 0, 1]
prodAtIndex(10,["ABCD",(1,2,3)]) # ['D', 2]

可以创建一个类来更无缝地操作虚拟产品列表:

class Products:

    def __init__(self,*values):
        self.values = values
        
    def __len__(self):
        result = 1
        for s in map(len,self.values): result *= s
        return result

    def __getitem__(self,index):
        if isinstance(index,slice):
            return (self[i] for i in range(len(self))[index])
        if index<0: index += len(self)
        result = []                        # product fed in reversed order
        for v in reversed(self.values):    # from least significant to most
            index,p = divmod(index,len(v)) # get digit in current 'base'
            result.insert(0,v[p])          # build product
        if index: raise IndexError         # will be zero if in range
        return tuple(result)

    def index(self,P):
        index = 0
        for v,p in zip(self.values,P):
            index *= len(v)
            index += v.index(p)
        return index

用法:

 p = Products("ABCD",(1,2,3,4,5))

 len(p)             # 20

 p[4]               # ('A', 5)
 p[-4]              # ('D', 2) 

 p.index(['A',5])   # 4 

 for prod in p[3:5]: print(prod)

 ('A', 4)
 ('A', 5)


 # For your example...

 m  = 3
 n  = 2
 b3 = Products(*[range(n)]*m)

 b3[5] # [1, 0, 1]

排列?

如果您真的想像拥有一个列表一样操作排列但不生成实际列表,您可以创建一个函数,在“虚拟”列表中为您提供第 N 个排列:

from math import factorial
def permCount(A,k): return factorial(len(A))//factorial(len(A)-k)
def permAtIndex(A,k,index):
    A      = list(A)
    base   = permCount(A,k)
    result = []
    for _ in range(k):                    # for required size
        base  //= len(A)                  # chunk size for position
        i,index = divmod(index,base or 1) # i is value index at position
        result.append(A.pop(i))           # build permutation
    return result 

这将为您提供排列“列表”的大小,并允许您在给定索引处获得排列:

print(permCount([1,2,3,4],4))       # 24
print(permAtIndex([1,2,3,4],4,22))  # [4, 3, 1, 2]

print(permCount(range(15),10))              # 10897286400
print(permAtIndex(range(15),10,5897286400)) # [8,1,10,5,12,0,13,7,14,9]

您还可以创建一个反向函数来为您提供给定排列的索引:

def indexOfPerm(A,k,P):
    A      = list(A)
    base   = permCount(A,k)
    result = 0
    for n in P:            
        base //= len(A)  # chunk size for position
        i = A.index(n)   # index in remaining items
        result += base*i # position's component of index
        A.pop(i)         # next position on rest of items
    return result

print(indexOfPerm([1,2,3,4],4,[4, 3, 1, 2])) # 22
print(indexOfPerm(range(15),10,[8,1,10,5,12,0,13,7,14,9])) # 5897286400

所有这些都可以转化为一个允许操作虚拟排列列表的类:

from math import factorial as fact
class Permutations:

    def __init__(self,values,size):
        self.values = values
        self.size   = size
        
    def __len__(self):
        return fact(len(self.values))//fact(len(self.values)-self.size)

    def __getitem__(self,index):
        if isinstance(index,slice):
            return (self[i] for i in range(len(self))[index])
        A      = list(self.values)
        base   = len(self)
        value  = []
        if index<0: index += len(self)
        for _ in range(self.size):            # for required size
            base  //= len(A)                  # chunk size for position
            i,index = divmod(index,base or 1) # i is value index at position
            value.append(A.pop(i))            # build permutation
        return tuple(value)

    def index(self,P):
        A      = list(self.values)
        base   = len(self)
        index  = 0
        for n in P:            
            base //= len(A)  # chunk size for position
            i = A.index(n)   # index in remaining items
            index += base*i  # position's component of index
            A.pop(i)         # next position on rest of items
        return index

用法:

p = Permutations(range(15),10)

print(len(p))                  # 10897286400

print(p[5897286400])           # (8, 1, 10, 5, 12, 0, 13, 7, 14, 9)
print(p[-100])                 # (14, 13, 12, 11, 10, 9, 8, 5, 4, 2)

print(p.index([8, 1, 10, 5, 12, 0, 13, 7, 14, 9])) # 5897286400


for perm in p[100:105]: print(perm)
(0, 1, 2, 3, 4, 5, 6, 9, 10, 13)
(0, 1, 2, 3, 4, 5, 6, 9, 10, 14)
(0, 1, 2, 3, 4, 5, 6, 9, 11, 7)
(0, 1, 2, 3, 4, 5, 6, 9, 11, 8)
(0, 1, 2, 3, 4, 5, 6, 9, 11, 10) 

请注意,用于非唯一值列表的这个 Permutations 类将产生重复的排列

【讨论】:

将整数转为组合或排列序列的正式名称为unranking:en.wikipedia.org/wiki/Combinatorial_number_system 众所周知。直到

以上是关于整数和数组排列之间的惰性映射,而不生成所有排列的主要内容,如果未能解决你的问题,请参考以下文章

484. 寻找排列

如何生成排列而不生成重复结果但具有固定数量的字符Python [重复]

生成字符数组的所有排列

当数组的数量和每个数组的长度未知时生成字符组合的所有排列

C语言数字全排列的问题(急!!)求C代码和算法

如何按排序顺序生成数组的所有排列?