在一组数中寻找加和最接近某个值的数组合

在一组数中寻找加和最接近某个值的数组合

今天碰到个小问题,就是需要在一组数中,找到加和数最接近某个值的一系列数。

比如:
[8.05, 6.98, 6.19, 5, 22.96,4.71,4.74,4.25,6.34,2.77,7.31,3.59,18.28,19.55]
中找到最接近84.01的一组数。

这个问题,所有的可能元素的加和组合数为16383,即:

Binomial[14, 1] + Binomial[14, 2] + Binomial[14, 3] + 
 Binomial[14, 4] + Binomial[14, 5] + Binomial[14, 6] + 
 Binomial[14, 7] + Binomial[14, 8] + Binomial[14, 9] + 
 Binomial[14, 10] + Binomial[14, 11] + Binomial[14, 12] + 
 Binomial[14, 13] + Binomial[14, 14]

用mathematica软件

如果求加和等于给定值的计算在mathematica软件中是很方便的,两句话就能搞定了。

array = Subsets[{8.05, 6.98, 6.19, 5, 22.96, 4.71, 4.74, 4.25, 6.34, 2.77, 7.31, 3.59, 18.28, 19.55}, 13];
Do[sumt = Total[array[[i]]];  If[sumt == 84.01, Print[array[[i]]]], {i, 1, Length[array]}]

结果为:

{6.98,6.19,5,22.96,4.71,4.74,4.25,7.31,3.59,18.28}

其中用到的函数是Subsets用于抽取list中元素的组合,13不带花括号则组合包括对所有数量小于等于13的情况。

如果要计算接近的情况,比如相差0.02的情况:

array = Subsets[{8.05, 6.98, 6.19, 5, 22.96, 4.71, 4.74, 4.25, 6.34, 
    2.77, 7.31, 3.59, 18.28, 19.55}, 13];
Do[err = Abs[Total[array[[i]]] - 84.01];
 If[err < 0.02, 
  If[err == 0, Print[err, array[[i]]]; Break[], 
   Print[err, array[[i]]]]], {i, 1, Length[array]}]

结果为:

0.02{8.05,6.19,22.96,4.71,4.25,18.28,19.55}
0.01{8.05,6.19,22.96,4.74,4.25,18.28,19.55}
0.02{8.05,22.96,4.25,7.31,3.59,18.28,19.55}
0.01{6.19,5,22.96,4.71,7.31,18.28,19.55}
0.02{6.19,5,22.96,4.74,7.31,18.28,19.55}
0.02{8.05,6.98,6.19,5,22.96,4.71,4.25,6.34,19.55}
0.02{8.05,6.98,5,22.96,4.25,6.34,7.31,3.59,19.55}
0.{6.98,6.19,5,22.96,4.71,4.74,4.25,7.31,3.59,18.28}

但实际上用mathematica软件做数值计算并不是非常合适,如果语句写的不好,那么很容易卡死。

使用python进行计算

其实最新想到的,并实践的是利用python来解决。这可能跟当前的知识应用状态相关,第一反应就是试图联系线性规划,如果有现成的求解器,那么就不用算了,但略一思索,似乎是不能用的。反而只是一些简单的加和,应该就可以解决。然后第二反应是怎么来解决,没有精确等于解的情况,我想这应该是一个范围,既能得到临界大于,也能得到临界小于的解。所以很自然想到排列,给出一个排列,那么从头计算到尾,自然能够得到最接近解的小于和大于的值。

所以首先实现了一个基于排列的蒙特卡洛方法解:

def MC_permutation():
	data1=[]
	res_data=[]
	res_j=0
	res_jp=0
	res_s1=0
	res_s2=0
	res_err=100.0
	outflag=False

	for i in range(200000):
		data1[:]=data[:]
		random.shuffle(data1) 
		#print("data1=",data1)
		sumt=0
		for j in range(len(data1)):
			sumts=sumt
			sumt=sumt+data1[j]
			if(sumt>suma):
				ds1=abs(sumts-suma)
				ds2=abs(sumt-suma)
				ds=min(ds1,ds2)
				if (ds < res_err):
					#print('')
					#print('ds=',ds)
					#print('i=',i,'data1=',data1)
					#print('j=',j,data1[j])
					#print('sum=',sumts,sumt)
					res_err=ds
					res_data[:]=data1[:]
					res_jp=j
					if (ds1 < ds2):
						res_j=j
					else:
						res_j=j+1
					#print(ds1,ds2)
					#print(j,res_j)
					res_s1=sumts
					res_s2=sumt
				if(ds<0.000001): outflag=True
				break
		if outflag: break

	print("")
	print('permutes=',res_data)
	print(res_jp,res_s1,res_s2)
	print('res_data=',sorted(res_data[:res_j]),' sum=',sum(res_data[:res_j]))
	print(res_s1,res_s2,res_err)

其核心是利用shuffle函数来进行排列的蒙特卡洛采样。

解决了问题以后,有点时间,我想看看利用组合其实也是一样的,那么遍历也是一样的,其实上面的mathematcia软件的计算就是遍历的方法。

基于组合的的蒙特卡洛方法解:

#利用组合的蒙特卡洛方法
def MC_combination():

	lendata=len(data)
	data1=[]
	res_data=[]
	res_st=0.0
	data1[:]=data[:]
	res_err=1000.0
	for i in range(20000):
		k=random.randrange(2,lendata+1) 
		data2=random.sample(data1, k)
		#print('i=',i,'k=',k)
		#print('data2=',data2)
		sumt=sum(data2)
		ds=abs(sumt-suma)
		if (ds < res_err):
			#print('')
			#print('ds=',ds)
			#print('i=',i,'data2=',data2)
			#print('k=',k)
			#print('sum=',sumt)
			res_err=ds
			res_data=data2
			res_st=sumt
			if( ds < 0.0000001): break
	
	print("")
	print('res_data=',sorted(res_data))
	print(res_st,res_err)

其核心是利用random.sample函数来进行组合的蒙特卡洛采样。

而遍历的方法为:

#利用遍历的方法
def sch_emuration():
	lendata=len(data)
	res_data=[]
	res_st=0.0
	res_err=1000.0
	i=0
	outflag=False
	for k in range(1,lendata+1):
		for da in itertools.combinations(data,k):
			i=i+1
			sumt=sum(da)
			#print('i=',i,' k=',k)
			#print('data=',da)
			#print('sumt=',sumt)
			ds=abs(sumt-suma)
			if (ds < res_err):
				#print('')
				#print('ds=',ds)
				#print('data=',da)
				#print('sum=',sumt)
				res_err=ds
				res_data[:]=da[:]
				res_st=sumt
				if(ds<0.0000001): 
					outflag=True
					break
					#pass
		if outflag: break
	
	print("")
	print('res_data=',sorted(res_data))
	print(res_st,res_err)

其核心是利用itertools.combinations遍历所有的组合。

运行结论:

三种方法都能快速的得到结果。

小结

运用mathematica和python对在一组数中寻找加和最接近某个值的数组合问题进行了求解。由于组合数不大,所以利用遍历的方法是不错的。
如果问题更大,那么可能用蒙特卡洛方法也许会在时间上取得优势。

附完整程序

#!/usr/bin/env python3
#_∗_coding: utf-8 _∗_


"""
在一组数中,找到加和数最接近某个值得一系列数。
比如在:
[8.05, 6.98, 6.19, 5, 22.96,4.71,4.74,4.25,6.34,2.77,7.31,3.59,18.28,19.55]
中找到最接近84.01的一组数
"""
import os
import string
import re
import sys
import datetime
import time
import copy
import json
import operator #数学计算操作符
import random
import itertools

data=[8.05, 6.98, 6.19, 5, 22.96,4.71,4.74,4.25,6.34,2.77,7.31,3.59,19.55] #18.28,
suma=84.01

#利用遍历的方法-列出附近的所有解
def sch_emuration_all(de):
	lendata=len(data)
	res_data=[]
	res_st=0.0
	res_err=1000.0
	i=0
	for k in range(1,lendata+1):
		for da in itertools.combinations(data,k):
			i=i+1
			sumt=sum(da)
			#print('i=',i,' k=',k)
			#print('data=',da)
			#print('sumt=',sumt)
			ds=abs(sumt-suma)
			if (ds < res_err):
				#print('')
				#print('ds=',ds)
				#print('data=',da)
				#print('sum=',sumt)
				res_err=ds
				res_data[:]=da[:]
				res_st=sumt
			if (ds <= de):
				print('err=%.2f'%ds,' sum=%.2f'%sumt,' data=',sorted(da))

	print("")
	print('res_data=',sorted(res_data))
	print(res_st,res_err)


#利用遍历的方法
def sch_emuration():
	lendata=len(data)
	res_data=[]
	res_st=0.0
	res_err=1000.0
	i=0
	outflag=False
	for k in range(1,lendata+1):
		for da in itertools.combinations(data,k):
			i=i+1
			sumt=sum(da)
			#print('i=',i,' k=',k)
			#print('data=',da)
			#print('sumt=',sumt)
			ds=abs(sumt-suma)
			if (ds < res_err):
				#print('')
				#print('ds=',ds)
				#print('data=',da)
				#print('sum=',sumt)
				res_err=ds
				res_data[:]=da[:]
				res_st=sumt
				if(ds<0.0000001): 
					outflag=True
					break
					#pass
		if outflag: break
	
	print("")
	print('res_data=',sorted(res_data),' sum=',sum(res_data))
	print(res_st,res_err)


#利用组合的蒙特卡洛方法
def MC_combination():

	lendata=len(data)
	data1=[]
	res_data=[]
	res_st=0.0
	data1[:]=data[:]
	res_err=1000.0
	for i in range(20000):
		k=random.randrange(2,lendata+1) 
		data2=random.sample(data1, k)
		#print('i=',i,'k=',k)
		#print('data2=',data2)
		sumt=sum(data2)
		ds=abs(sumt-suma)
		if (ds < res_err):
			#print('')
			#print('ds=',ds)
			#print('i=',i,'data2=',data2)
			#print('k=',k)
			#print('sum=',sumt)
			res_err=ds
			res_data=data2
			res_st=sumt
			if( ds < 0.0000001): break
	
	print("")
	print('res_data=',sorted(res_data),' sum=',sum(res_data))
	print(res_st,res_err)
	
	



#利用排列的蒙特卡洛方法
def MC_permutation():
	data1=[]
	res_data=[]
	res_j=0
	res_jp=0
	res_s1=0
	res_s2=0
	res_err=100.0
	outflag=False

	for i in range(200000):
		data1[:]=data[:]
		random.shuffle(data1) 
		#print("data1=",data1)
		sumt=0
		for j in range(len(data1)):
			sumts=sumt
			sumt=sumt+data1[j]
			if(sumt>suma):
				ds1=abs(sumts-suma)
				ds2=abs(sumt-suma)
				ds=min(ds1,ds2)
				if (ds < res_err):
					#print('')
					#print('ds=',ds)
					#print('i=',i,'data1=',data1)
					#print('j=',j,data1[j])
					#print('sum=',sumts,sumt)
					res_err=ds
					res_data[:]=data1[:]
					res_jp=j
					if (ds1 < ds2):
						res_j=j
					else:
						res_j=j+1
					#print(ds1,ds2)
					#print(j,res_j)
					res_s1=sumts
					res_s2=sumt
				if(ds<0.000001): outflag=True
				break
		if outflag: break

	print("")
	print('permutes=',res_data)
	print(res_jp,res_s1,res_s2)
	print('res_data=',sorted(res_data[:res_j]),' sum=',sum(res_data[:res_j]))
	print(res_s1,res_s2,res_err)


				
if __name__ == "__main__":

	#遍历的方法
	t1=time.process_time()
	sch_emuration()
	t2=time.process_time()
	print('time elapsed',t2-t1)

	#排列的MC方法
	t1=time.process_time()
	MC_combination()
	t2=time.process_time()
	print('time elapsed',t2-t1)
	

	#组合的MC方法
	t1=time.process_time()
	MC_permutation()
	t2=time.process_time()
	print('time elapsed',t2-t1)

	'''
	datan=sorted([3.59, 6.19, 7.31, 4.25, 18.28, 22.96, 4.71, 6.98, 5, 4.74])
	print(datan)
	print(sum(datan))
	'''

	print("")
	sch_emuration_all(0.05)
	'''

### 回答1: 在 R 语言中,`mosaicplot()` 是一种用于绘制马赛克图的函。马赛克图是一种用于显示两个或更多变量之间关系的图形,其中每个矩形代表一个单元格,并且单元格的大小表示该组据集中出现的频率。 当超过预期的频率或期望在某些单元格中出现时,称为“超差”(residuals),也可以称为“残差”。这些残差可能表示了据中的某些趋势或关系,因此对于发现和理解据中的这些趋势和关系非常有用。在 `mosaicplot()` 中,可以使用 `shade` 参来突出显示残差。 因此,在 `mosaicplot()` 中超过预期的残差是指某些组的观察频率与期望频率之间的差异,可以通过使用 `shade` 参来突出显示这些残差。当 `shade=TRUE` 时,`mosaicplot()` 会使用颜色编码来突出显示超出预期的频率或残差。 ### 回答2: 在R语言的mosaicplot中,超过残差预期意味着观察到的在两个或多个分类变量之间的关系比预期的关系更强烈或有更高的相关性。例如,在一个交叉分类表中,残差预期是根据行与列变量的独立性来计算的,如果观察到的据显示某些分类组的频明显高于预期,那么这些组将呈现出超过残差预期的情况。 超过残差预期的情况可能表示两个或多个分类变量之间存在某种关联或相互影响。这种关联或相互影响可能是由于随机性或偶然性导致的,也可能是由于真实的因果关系或其他未知因素导致的。 通过mosaicplot中超过残差预期的现象,我们可以得出两个分类变量之间存在关联的初步推断。这可以为进一步的研究提供指导,例如建立适当的统计模型或进行更详细的据分析以验证这种关联的可靠性。 ### 回答3: 在R语言中,使用mosaicplot函绘制的马赛克图(mosaic plot)可以用于可视化多个分类变量之间的关系。其中,超过残差预期的意思是某个分类变量在两个或多个其他分类变量之间的相关性大于预期。 具体来说,残差预期是根据每个分类变量的边际比例计算得出的。如果两个分类变量之间存在关联,那么马赛克图中的矩形区块(tiles)的颜色会相对平均分布,即呈现出一种期望的均衡状态。如果某个矩形区块的颜色比预期接近某一侧(亮色或暗色),则表示该分类变量在两个或多个其他分类变量之间存在显著的正相关或负相关。 因此,超过残差预期的意思是某个矩形区块的颜色偏离了期望的均衡状态,表明该分类变量在其他分类变量之间具有较强的相关性。这一现象可能暗示着一个有趣的关联关系,需要进一步的据分析与解释。用户可以通过观察马赛克图中的颜色分布情况,来揭示分类变量之间的关系以及背后的统计意义。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值