如何筛选满足首元素出现顺序条件的数组多重排列?
解决方案
要筛选出符合要求的排列,需同时满足两个核心条件:0、1、2、3的首次出现顺序严格遵循0→1→2→3,以及每个数字的第二次出现位置在其首次出现位置右侧(注:multiset_permutations生成的排列天然满足第二个条件,代码中保留判断以确保严谨性)。
方法一:逐行循环判断(易理解)
逻辑直观,适合小数据量场景:
import numpy as np from sympy.utilities.iterables import multiset_permutations # 生成所有排列 a = np.array([0, 0, 1, 1, 2, 2, 3, 3]) out = np.array(list(multiset_permutations(a))) # 定义验证函数 def is_valid(p): # 检查首次出现顺序 first_idx = [np.argmax(p == x) for x in [0,1,2,3]] if not (first_idx[0] < first_idx[1] < first_idx[2] < first_idx[3]): return False # 检查第二次出现位置在首次之后 for x in [0,1,2,3]: pos = np.where(p == x)[0] if pos[1] <= pos[0]: return False return True # 筛选有效排列 valid_perms = out[np.array([is_valid(row) for row in out])]
代码说明:
np.argmax(p == x):找到数字x在排列中首次出现的索引(布尔数组的第一个True位置)。- 首次出现顺序判断:确保0的首次位置早于1,1早于2,2早于3。
- 第二次出现位置判断:通过
np.where获取数字x的所有出现位置,验证第二个位置索引大于第一个。
方法二:向量化批量处理(高效)
当排列数量较大(比如5对数字时总排列数达113400),用numpy向量化操作可大幅提升效率:
import numpy as np from sympy.utilities.iterables import multiset_permutations a = np.array([0, 0, 1, 1, 2, 2, 3, 3]) out = np.array(list(multiset_permutations(a))) # 目标数字列表(扩展到5对时改为[0,1,2,3,4]) targets = np.array([0,1,2,3]) # 1. 计算每一行中每个目标数字的首次出现索引 matches = out[:, None] == targets[None, :, None] # 广播为(N,4,8)的布尔数组 first_occur = matches.argmax(axis=2) # (N,4),每行对应4个数字的首次索引 # 2. 验证首次出现顺序严格递增 cond1 = (first_occur[:,0] < first_occur[:,1]) & (first_occur[:,1] < first_occur[:,2]) & (first_occur[:,2] < first_occur[:,3]) # 3. 验证每个数字的第二次出现位置在首次之后 last_occur = matches.cumsum(axis=2).argmax(axis=2) # 计算最后出现索引(即第二次出现位置) cond2 = (last_occur > first_occur).all(axis=1) # 所有数字的最后位置都大于首次位置 # 4. 筛选有效排列 valid_mask = cond1 & cond2 valid_perms = out[valid_mask]
代码说明:
- 广播匹配:通过
out[:, None] == targets[None, :, None]一次性对比所有行与所有目标数字的匹配情况,避免循环。 - 首次/最后出现索引:
argmax(axis=2)找首次匹配位置;cumsum(axis=2).argmax(axis=2)通过累计求和找到最后一次匹配的位置(即第二次出现的索引)。 - 条件合并:用布尔掩码批量筛选所有符合条件的排列,效率远高于逐行循环。
扩展到5对数字的场景
只需将代码中的targets改为np.array([0,1,2,3,4]),并调整cond1的判断逻辑为:
cond1 = (first_occur[:,0] < first_occur[:,1]) & (first_occur[:,1] < first_occur[:,2]) & (first_occur[:,2] < first_occur[:,3]) & (first_occur[:,3] < first_occur[:,4])
其余代码无需修改即可直接使用。
内容的提问来源于stack exchange,提问作者user109387
相关产品推荐
相关产品推荐

