用 0 和 1 填充的 numpy 二维数组的所有组合

Posted

技术标签:

【中文标题】用 0 和 1 填充的 numpy 二维数组的所有组合【英文标题】:All combinations of a numpy 2d array filled with 0s and 1s 【发布时间】:2022-01-09 16:08:16 【问题描述】:

给定 K,我需要拥有 K x 2 numpy 矩阵的所有可能组合,以便在每个矩阵中,除了不同行和列中的两个 1 之外,所有的都是 0。 对于 K = 5 是这样的:

    [[1,0],[0,1],[0,0],[0,0][0,0]] [[1,0],[0,0],[0,1],[0,0][0,0]] [[1,0],[0,0],[0,0],[0,1][0,0]] [[1,0],[0,0],[0,0],[0,0][0,1]] [[0,0],[1,0],[0,1],[0,0][0,0]] [[0,0],[1,0],[0,0],[0,1][0,0]] ...等等

所以结果数组应该是 K x 2 x (K*(K-1)/2)。 我想避免循环,因为当 K 足够大时(在我的特定情况下 K = 300),这不是一种有效的方法

【问题讨论】:

【参考方案1】:

我想不出一个优雅的解决方案,但这里有一个不太优雅的纯 numpy 解决方案:

import numpy as np

def combination_matrices(K):
    # get combination indices
    i, j = np.indices((K, K))
    comb_indices = np.transpose((i < j).nonzero())  # (num_combs, 2) array where ones are
    num_combs = comb_indices.shape[0]  # K*(K-1)/2

    # create a matrix of the desired shape, first axis enumerates combinations
    matrices = np.zeros((num_combs, K, 2), dtype=int)
    # broadcasting assignment of ones
    comb_range, col_index = np.ogrid[:num_combs, :2]
    matrices[comb_range, comb_indices, col_index] = 1
    return matrices

这首先使用(K, K) 形数组的索引来查找每个组合的索引对(这些索引对数组的上三角形进行编码,不包括对角线)。然后我们使用有点棘手的广播赋值(重 fancy indexing)将预分配的输出数组的每个对应元素设置为 1。

请注意,我将 K*(K-1)/2 大小的轴放在首位,因为这在具有 C 连续内存布局的 numpy 中最有意义。这样,当您将矩阵用于组合索引 3 时,arr[3, ...] 将是形状为 (K, 2) 的连续内存块,在矢量化操作中可以快速使用。

K = 4 的输出:

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

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

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

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

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

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

【讨论】:

谢谢!这正是我需要的。我只有一个问题:我需要沿 K 轴访问这个数组,所以将 K 作为第一个维度可能会更好?我不是内存分配方面的专家 @mariottidae 在这种情况下,您可能只需将分配更改为matrices = np.zeros((K, 2, num_combs), dtype=int) 并将分配更改为matrices[comb_indices, col_index, comb_range] = 1。这将为您提供一个形状数组(K, 2, num_combs)。相当于在我的原始版本上调用.transpose(1, 2, 0),但这将再次是内存中的一个连续数组(与转置不同)。【参考方案2】:

这是一个奇怪的具体问题,但是一个有趣的问题,我很想知道上下文是什么?

您正在寻找多重集合 的所有排列,python 的 itertools 目前不支持此功能。所以最简单的解决方案是使用sympy 库的多集工具。

以下代码在我的机器上运行大约需要 2.5 分钟,因此对于单线程来说相当快。您正在查看 K=300 的 179700 唯一排列。

(我的灵感来自https://***.com/a/40289807/10739860)

from collections import Counter
from math import factorial, prod

import numpy as np
from sympy.utilities.iterables import multiset_permutations
from tqdm import tqdm


def No_multiset_permutations(multiset: list) -> int:
    """Calculates the No. possible permutations given a multiset.
    See: https://en.wikipedia.org/wiki/Permutation#Permutations_of_multisets

    :param multiset: List representing a multiset.
    """
    value_counts = Counter(multiset).values()
    denominator = prod([factorial(val) for val in value_counts])
    return int(factorial(len(multiset)) / denominator)


def multiset_Kx2_permutations(K: int) -> np.ndarray:
    """This will generate all possible unique Kx2 permutations of an array
    withsize K where two values are 1 and the rest are 0.

    :param K: The size of the array.
    """
    # Construct number multiset, e.g. K=5 gives [1, 1, 0, 0, 0, 0, 0, 0, 0, 0]
    numbers = [1, 1] + [0] * (K - 1) * 2

    # Use sympy's multiset_permutations to get a multiset permutation generator
    generator = multiset_permutations(numbers)

    # Calculate the No. possible permutations
    number_of_perms = No_multiset_permutations(numbers)

    # Get all permutations, bonus progress bar is included :)
    unique_perms = [next(generator) for _ in tqdm(range(number_of_perms))]

    # Reshape each permutation to Kx2
    unique_perms = np.array(unique_perms, dtype=np.int8)
    return unique_perms.reshape(-1, K, 2)


if __name__ == "__main__":
    solution = multiset_Kx2_permutations(300)

【讨论】:

【参考方案3】:

另一种可能性(重新排列轴以获得更清晰的输出):

from itertools import combinations
import numpy as np

k = 4
x = list(combinations(range(k), 2))
out = np.zeros((n := len(x), k, 2), dtype=int)
out[np.c_[:n], x, [0, 1]] = 1
print(out)

它给出:

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

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

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

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

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

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

【讨论】:

以上是关于用 0 和 1 填充的 numpy 二维数组的所有组合的主要内容,如果未能解决你的问题,请参考以下文章

根据一维计数器数组填充二维数组列

零填充numpy数组

Numpy

numpy数组-二维数组的轴

如何在 2D numpy 数组中查找簇大小?

在二维 numpy 数组中查找匹配的行