(6/28 小更新)Excel多組數字挑選加總為指定數值的方法(vba 範例)

kore5657 wrote:
不好意思...我忘了還有一個很重要的條件
我的問題中並不用考慮組合的順序...
例如:
100 = 8+8+8+8+8+8+8+8+8+8+10+10
不管組合內的數字怎麼替換順序,都只算一種


這是可重複選取的不重複組合公式
而我原來的範例是不可重複選取的不重複組合公式

不重複組合、重複組合,雖然公式不同
但在我寫的vba範例中,計算用的程式碼,只有差在其中一個變數 +1 改成 +0
請自行和4樓舊版程式碼比較看看有何不同







測試:7取1=>7取2=>…7取15,有170543種組合,加總100的365組,約0.9秒可計算完




Sub test()

Dim data(), targetsum() As Double, item_name() As String, lastrow As Integer, ttt As Double
Dim total As Long, i As Integer, ok As Integer, target As Double, limited As Integer, maxcombin As Integer, start As Integer, startend As Integer, tolerance As Double, rs As Boolean

Columns("E:Z").Clear

'esc中斷功能,計算極大量資料時無效,程式會來不及回應
On Error GoTo Esc_Stop
Application.EnableCancelKey = xlErrorHandler

ttt = Timer

lastrow = Range("A2").End(xlDown).Row
ReDim data(1 To 2, 1 To lastrow - 1)

For d = 1 To lastrow - 1
data(1, d) = Cells(d + 1, 2).Value
' data(2, d) = Cells(d + 1, 1).Value
Next d

If Range("C1") = 0 Then End

target = Range("C1"): tolerance = Range("C2"): limited = Range("C3"): maxcombin = Range("C4")
Range("C5") = "": Range("C8") = "": Range("E1") = Format(Now(), "hh:mm:ss")

If Range("c6") = "" Then
Range("c6") = False
Range("c7") = ""
End If

If Range("c6") = True And Range("c7") = "" Then Range("c7") = 15

rs = Range("c6")

If limited = 0 Then
start = 1
If rs = True Then startend = Range("c7") Else startend = UBound(data, 2)
Else
start = limited: startend = start
End If

For i = start To startend
ReDim targetsum(1 To i)
' ReDim item_name(1 To i)
Call findCombin(data, targetsum, item_name, i, total, 1, 1, ok, target, limited, maxcombin, tolerance, rs)
If start <> startend Then Range("C8") = Int((i / startend * 100) + 0.5) & "%請耐心等待"
Next i

If ok <> 0 Then Call find_name

Range("C8") = "100%計算結束": Range("E2") = Format(Now(), "hh:mm:ss")
Debug.Print total, Timer - ttt

Exit Sub

Esc_Stop:
If Err = 18 Then
MsgBox "stop"
End If

End Sub

Sub findCombin(data(), targetsum() As Double, item_name() As String, j As Integer, total As Long, k As Integer, n As Integer, ok As Integer, target As Double, limited As Integer, maxcombin As Integer, tolerance As Double, rs As Boolean)

Dim i As Integer, tempsum As Double

For i = k To UBound(data, 2)

targetsum(n) = data(1, i): tempsum = 0
' item_name(n) = data(2, i)

If n = j Then
total = total + 1

For s = 1 To UBound(targetsum)
tempsum = tempsum + targetsum(s)
Next s

If tempsum = target Then '把變數tolerance代入這一行,就可增加誤差值功能
ok = ok + 1

Range("G" & ok).Resize(1, j) = targetsum
' Range("F" & ok) = Join(item_name, ",")

If (ok = maxcombin And maxcombin <> 0) Then
Range("C5") = ok: Range("C8") = "100%計算結束": Range("E2") = Format(Now(), "hh:mm:ss"): Debug.Print total
If ok <> 0 Then Call find_name
End
End If

Range("c5") = ok

End If

Else
Call findCombin(data, targetsum, item_name, j, total, i + IIf(rs, 0, 1), n + 1, ok, target, limited, maxcombin, tolerance, rs)
End If

Next i

End Sub

Sub find_name()

Dim lastrow As Integer, lastcol As Integer, n As Object, name_data() As String
Set n = CreateObject("Scripting.Dictionary")
Application.ScreenUpdating = False

lastrow = Range("A1").End(xlDown).Row
For i = 2 To lastrow
n.Add Str(Cells(i, 2)), Cells(i, 1)
Next i


For i = 1 To Range("c5").Value
lastcol = Range("G" & i).End(xlToRight).Column
ReDim name_data(1 To lastcol - 6)

For j = 7 To lastcol
name_data(j - 6) = n(Str(Cells(i, j)))
Next j

Cells(i, 6) = Join(name_data, ",")

Next i

Set n = Nothing
Application.ScreenUpdating = True

End Sub





[點擊下載]


誤差值功能保留,想要請自行練習修改
snare wrote:
這是可重複選取的不重(恕刪)


非常感謝S大大的幫助!!
snare wrote:
(***此文只在mobile01...(恕刪)


謝謝樓主的程式,我現在正好需要用上。
請教一下,因數值需要有小數位,我亦在原先的"Dim i As Long, tempsum As Long",改為"Dim i As Integer, tempsum",但不知為何程式運行到了16%後,左上角顯示"沒有回應",久久沒有回復正常,按了ESC停止也沒有回復正常,最後要強行關閉excel。

目標值以下的幾個項目,如下!我都留空了,因不需要!
<=誤差值(不需正負號,可不填)
<=每組限定個數(不填就全找)
<=最大搜尋組數(不填就全找)
<=找到的組數

另暫時只有38個數值需要運算,但程式都在16%時沒有回應了。
GordonYiu
妳要不要直接把檔案附上來,你是不是都填了有小數點的數值呢?我直接將你要的功能寫好給你。不需要再改寫。
pretty_woman
謝謝大大!但38個編號的數字照樓主的意思,好像不能運行。大大有其他方法嗎?
GordonYiu wrote:
看了一下樓主最新的11樓,就已經更新成可以接受小數了,你可以下載第11樓的excel 檔。(恕刪)


謝謝GordonYiu ,幫我測試這古董範例

補充:
重新檢查會支援小數的原因,說來慚愧,原來只是我不小心
範例本來就只考慮整數的排列組合

小數直接執行會有小bug,要完整支援小數,只需要把程式碼中的
as single 全部改成 as double 就行,其它不變

pretty_woman wrote:
另暫時只有38個數值需要運算,但程式都在16%時沒有回應了。


重複選取(true)
38個看似很少,但對重複選取的排列組合來說是天文數字
9樓有解釋為什麼算不完

不可重複選取時(false),38個,程式要跑
2^n-1 = 274877906943 (次)
正常情況下不是短時間能算完的


補充測試…
25組、有小數點、不重複、全找,我的電腦約2分鐘
30組、有小數點、不重複、全找,我的電腦從21:08分~到現在21:38還沒跑完,我強制中斷執行

所以您的38組…
程式中最大搜尋組數,不能留空白
要把搜尋組數做個限制才行,10組、20組…


至於百分比%停住,因為我偷懶用資料組做計算,例如10筆,1/10、2/10…
但排列組合計算是愈後面計算量愈大,可以考9樓的次數
所以會有卡住的錯覺,其實是excel還在跑,資料太多算不完
pretty_woman
剛我在做測試,用不可重複選取的模式,跑了35分鐘至34%就不再運行了,請問大大還有其他方法比較適合較多編號的數據嗎?
pretty_woman wrote:
剛我在做測試,用不可重複選取的模式,跑了35分鐘至34%就不再運行了,請問大大還有其他方法比較適合較多編號的數據嗎?


不是不再運行,是還沒跑完

38個
2^n-1=274877906943

我的電腦跑最簡單的6000000次迴圈,需要0.1875秒



全跑完要(274877906943/6000000)*0.1875/60/60=2.386小時
這還不含其它計算,程式碼只有1行簡單的加法,超精簡的寫法都要這麼久了

如果您不對搜尋組數做限制,要把所有的可能性全列出來
不好意思,我excel 能力有限,寫不出能短時間內計算出來程式碼
沒辦法給您什麼建議

反正您在這篇的用途是“”可能組合,正確性不重要
https://www.mobile01.com/topicdetail.php?f=511&t=6613952&p=2
全找出來也不知道正確的是那個
就算只找一組,還不是一樣

不過還是建議,跟錢有關的,不要用猜的,要100%正確才行
pretty_woman
我的情況是B欄的數值有38個,將來亦有可能會更多一點 (A欄編號排A-Z也不夠,要加多些編號如A1,A2,A3....),已選了用FALSE,程式一直顯示沒有回應,甚至畫面都白了
pretty_woman
我不介意多等1-2個小時,只要程式最終能在幾小時內運作完就好,亦不會使用TRUE去運作,但程式就是不知能否做到。
GordonYiu wrote:
這還真是有趣,雖然是一個小小的程式,先不要那麼早放棄。樓主有沒有考慮到long資料型態的問題呢?long 只能支援到– 2,147,483,648 and 2,147,483,647,大概是10位數。





謝謝您幫我找問題,我確實沒注意到Long位數的問題,但要等的時間夠長才會出現錯誤
不過問題只會出現在total這個變數,超出時,程式出會現”溢位”錯誤中斷
等的不夠久,只會有excel跑不動的情況,程式不會出錯







以15樓超精簡範例的時間來算,Long最少可以撐1.12小時
如果加上排列組合計算,實際時間可以更長,大約3.3小時
目前的問題是計算次數太多,還沒等到overflow錯誤時
就會手動強制關掉excel了


因為程式寫法的關係,找到的結果直接放表格,沒找到就不理它
不會有計算次數的問題(不重複選取)
每次迴圈最大值,就是放進去組數(例如:38組)

去掉total變數,就可以一直算下去,甚至超出12位數,只有時間很長~很長~的問題


GordonYiu wrote:
2^38-1=274,877,906,943有12位數,會不會是這問題呢?另外還有沒有其他的演算法呢?只能這樣硬碰硬嗎?只能把這麼多組合都算出來再比對嗎?





等的時間夠久才是位數問題,目前是excel算不動

排列組合,要列出"所有解"
我所知道的演算法,只有硬碰硬這個方法
因為算法不管怎麼改,最後要跑的次數還是不變1 ~ 2^n-1
如果是vba寫法,那還有Dictionary法,但內容還是硬碰硬

像excel的規劃求解、也可以處理排列組合
不同的是,找到第一組就結束,這就是所謂的最佳(可能)解找法
所以規畫求解,有個很有趣的現象,當資料量比較大時
算出來的結果是變動的,不一定每次都一樣

因此我才會在程式碼加上搜尋組數限制功能,避免跑不完
有限制的話,也可以代替excel規劃求解,在只找一組、相同條件下
這個vba範例比excel規劃求解,快很多


pretty_woman 他要的是所有解,不設定上限,不是找最佳(可能)解,所以跑不完
pretty_woman
剛用樓主在11樓的檔案再跑一下,我將TRUE改為FALSE,沒有改動樓主檔案內的任何數字,但檔案在F欄開始就空白了,應該是程式沒有運行的樣子。
snare
因為11樓範例內的7個數值(8+10+12+14+16+18+20=98),在"不可重複選取false"的情況下,加總不可能到100,目標值條件不成立,因無解才空白。您可以把其中一個值+2,再跑一次。
snare wrote:
稍微更新一下,多加上(恕刪)

請問假設100只設定了一項
為什麼後續的組合100卻會被重複使用
EX
100 200 300
100 300 400
100 400 500

能不能讓被組合過的數字在不同組也不再使用?
EX
100 200 300
500 600 700
800 900 1000
nezher wrote:
請問假設100只設定了一項
為什麼後續的組合100卻會被重複使用


看不太懂您的發問?
排列組合,是用位置來排的,不是用內容
內容是用來決定條件、篩選結果的,例加:加總
跟排列組合總數無關

nezher wrote:
能不能讓被組合過的數字在不同組也不再使用?


這樣的條件很奇怪,先不管加總的問題
例如1、2、3,有以下幾種情形,如果要讓出現過的不再出現
那麼只有前3組可成立,後面的無論如何都會重複到
1、2、3、4、5,只有前5組可成立

您的問題比較像是“分類、分組、隨機抽樣、機率”之類的?

https://zh.wikipedia.org/zh-tw/组合数学

snare wrote:
看不太懂您的發問?排(恕刪)

我希望每個位置的數值被使用過後
就不會再被使用
就是每個位置的數值都只被使用一次去組合成目標值
比如100只有一個 那後面的排列不要再出現100

稍微看了一下程式
假設資料原本是 500, 400, 300, 200, 100
目標數值是 700
程式計算到 500 + 400 時
不會判斷數值已經超過了,會繼續往下測試 500 + 400 + 300 等數值
這會浪費很多計算時間
感覺可以優化加快速度
關閉廣告
文章分享
評分
評分
複製連結

今日熱門文章 網友點擊推薦!