引子
之所以想起来itertools,是看到了这么一个题目
题目是英文的,里面废话有点多,简单而言就是:
假设有一组只由0
和1
构成字符串,你可以随便猜3次,每次告诉你猜对了多少位。
然后问题来了,你要结合你这三次猜测结果,说说满足这个条件的字符串最多存在几个。
试着用python3写了一下解法:
"""这一段是2018年4月写的
# 载入数据
x = '6 3'
length,trytimes = [int(i) for i in x.split(' ')]
print(length,trytimes )
y = '''
000000 2
010100 4
111100 2
'''
r = [j.split(' ') for j in [i for i in y.strip().split('\n')]]
print(r)
# 比较两个字符串,返回相同位置上,字符一致的个数。
def match(a,b):
count = 0
if len(a)==len(b):
for i in range(len(a)):
if a[i]==b[i]:
count+=1
else:
print('ERROR:',a,b)
return str(count)
# 开始解题,构造字符串迭代器,遍历所有字符串,返回符合条件的字符串个数
import itertools
total = 0
z = itertools.product('01', repeat=length)
for i in z:
g = ''.join(i)
for j in range(trytimes):
if match(r[j][0],g)!=r[j][1]:
break
else:
total+=1
# 符合条件的
print(total)
"""
"""======================================
2021-03-04, 时隔3年再看, 重写一下
======================================"""
import numpy as np
from io import StringIO
from itertools import product
y = '''
000000 2
010100 4
111100 2
'''
narr= np.loadtxt(StringIO(y), delimiter=" ", dtype=[('sample', 'O'), ('matchNum', '<i4')])
sampleLength = len(narr[0][0])
def countMatch(x,y):
return [char1==char2 for char1,char2 in zip(x,y)].count(True)
matchStrArr = list()
for enumvar in product('01', repeat=sampleLength):
for sample,matchNum in narr:
if countMatch(enumvar,sample)!=matchNum:
break
else:
matchStrArr.append(''.join(enumvar))
print(len(matchStrArr),matchStrArr)
用itertools实现排列组合
因为昨天刚刚说完关于迭代器的特征,这里itertools实际上就是通过迭代器的方式,相当高效且节省内存。
将itertools中的排列组合,拿出来专门记录一下:
以“4个中抽取2个”为例(4个一样的乒乓球,分别写上ABCD,扔到不透明的箱子里。在看不到球的情况下,每次摸出1个球,摸2次),在不同的判定标准下,列出所有可能出现的结果
函数 | 名称 | 说明 | 每种方式得到的结果数(数学计算公式) |
---|---|---|---|
combinations | 组合 | 不分先后(AB==BA) | C 4 2 = 4 ! 2 ! ( 4 − 2 ) ! = 4 ∗ 3 ∗ 2 ∗ 1 2 ∗ 1 ∗ 2 ∗ 1 = 6 C_4^2=\frac{4!}{2!(4-2)!}=\frac{4*3*2*1}{2*1*2*1}=6 C42=2!(4−2)!4!=2∗1∗2∗14∗3∗2∗1=6 |
permutations | 排列 | 分先后(AB!=BA) | A 4 2 = 4 ! ( 4 − 2 ) ! = 4 ∗ 3 ∗ 2 ∗ 1 2 ∗ 1 = 12 A_4^2=\frac{4!}{(4-2)!}=\frac{4*3*2*1}{2*1}=12 A42=(4−2)!4!=2∗14∗3∗2∗1=12 |
combinations_with_replacement | 组合(放回) | 有放回,不分先后(AB==BA且存在AA,BB…) | C 4 + 2 − 1 2 = 5 ! 2 ! ( 5 − 2 ) ! = 5 ∗ 4 ∗ 3 ∗ 2 ∗ 1 2 ∗ 1 ∗ 3 ∗ 2 ∗ 1 = 10 C_{4+2-1}^2=\frac{5!}{2!(5-2)!}=\frac{5*4*3*2*1}{2*1*3*2*1}=10 C4+2−12=2!(5−2)!5!=2∗1∗3∗2∗15∗4∗3∗2∗1=10 |
product | 排列(放回) | 有放回,分先后(AB!=BA且存在AA,BB…) | 4 2 = 16 4^2=16 42=16 |
# 为了输出结果好看,加了list(map(lambda x:''.join(x)……
print(list(map(lambda x:''.join(x),itertools.combinations('ABCD', 2))))
print(list(map(lambda x:''.join(x),itertools.permutations('ABCD', 2))))
print(list(map(lambda x:''.join(x),itertools.combinations_with_replacement('ABCD', 2))))
print(list(map(lambda x:''.join(x),itertools.product('ABCD', repeat=2))))
['AB', 'AC', 'AD', 'BC', 'BD', 'CD']
['AB', 'AC', 'AD', 'BA', 'BC', 'BD', 'CA', 'CB', 'CD', 'DA', 'DB', 'DC']
['AA', 'AB', 'AC', 'AD', 'BB', 'BC', 'BD', 'CC', 'CD', 'DD']
['AA', 'AB', 'AC', 'AD', 'BA', 'BB', 'BC', 'BD', 'CA', 'CB', 'CC', 'CD', 'DA', 'DB', 'DC', 'DD']
上面例子中的最后一项“排列(放回)”,其实就是笛卡尔积。所以也可以写成for循环嵌套。
for i in 'ABCD':
for j in 'ABCD':
print(i+j,end=' ')
AA AB AC AD BA BB BC BD CA CB CC CD DA DB DC DD
每种结果出现的概率
2022-10-08 添加此段内容
python中存在一个random模块,用于构建随机事件,这使得我们想要粗略计算概率时,可以直接通过穷举的方法来计算每种结果出现的概率。
以上方的四种抽取方式为例:
import random
import itertools
def buildsample(method):
'''按照指定的排列组合方式,构建出1个随机样本'''
if method==0:
sample = sorted(random.sample(Seed, ChoiceTimes)) # 组合(不分先后)
elif method==1:
sample = random.sample(Seed, ChoiceTimes) # 排列(分先后)
elif method==2:
sample = sorted([random.choice(Seed) for t in range(ChoiceTimes)]) # 组合(放回)(不分先后)
elif method==3:
sample = [random.choice(Seed) for t in range(ChoiceTimes)] # 排列(放回)(分先后)
else:
raise '未知方法'
return tuple(sample)
# 随机生成样本,统计每种结果出现的频率
MethodDict = {
0:'组合(不分先后)',
1:'排列(分先后)',
2:'组合(放回)(不分先后)',
3:'排列(放回)(分先后)'
}
Seed = 'ABCD' # 几个球
ChoiceTimes = 2 # 抽几次
RandomTimes = 1000000 # 随机次数
# 4种模式中,每个模式每种结果的概率
for method in MethodDict:
ResultDict = dict() # 用于记录每种结果出现的次数
for i in range(RandomTimes): # 随机生成,成千上万次,统计每种结果出现的次数
sample = buildsample(method)
ResultDict[sample] = ResultDict.get(sample,0)+1
d = {''.join(k):round(v/RandomTimes, 3) for k,v in ResultDict.items()} # 将每种结果出现的次数转为概率
l = sorted(d.items(), key=lambda x:x[1], reverse=True) # 按概率从大到小排序
# 输出计算结果
print(f'{MethodDict[method]}的{len(l)}种结果各自概率:')
[print(k,v) for k,v in l]
组合(不分先后)的6种结果各自概率:
AD 0.167
BD 0.167
CD 0.167
BC 0.166
AC 0.166
AB 0.166
排列(分先后)的12种结果各自概率:
DB 0.084
DA 0.084
BD 0.084
CA 0.084
BC 0.083
CB 0.083
AB 0.083
AC 0.083
AD 0.083
DC 0.083
BA 0.083
CD 0.083
组合(放回)(不分先后)的10种结果各自概率:
BC 0.125
AD 0.125
BD 0.125
AC 0.125
AB 0.125
CD 0.125
DD 0.063
CC 0.063
BB 0.062
AA 0.062
排列(放回)(分先后)的16种结果各自概率:
CA 0.063
CB 0.063
DD 0.063
AA 0.063
DA 0.063
DC 0.063
AB 0.063
BB 0.063
CD 0.063
AC 0.063
AD 0.062
BA 0.062
DB 0.062
CC 0.062
BD 0.062
BC 0.062
通过以上数据,我们可以近似得出“4抽2”在不同情况下,每种结果的概率:
4抽2方式 | 存在多少种结果 | 每种结果出现的概率 |
---|---|---|
组合(不分先后) | 6 | 1 6 \frac{1}{6} 61 |
排列(分先后) | 12 | 1 12 \frac{1}{12} 121 |
组合(放回)(不分先后) | 10 | AB这种为 1 8 \frac{1}{8} 81,AA这种为 1 16 \frac{1}{16} 161 |
排列(放回)(分先后) | 16 | 1 16 \frac{1}{16} 161 |