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

在這篇看到,材料長度、需求數量、最佳解的問題
https://www.mobile01.com/topicdetail.php?f=511&t=6656360

其實這也是排列組合的一種,只取1解
(不代表一定只有1解,所以可能不是最佳解)
不需要太多計算,算是簡化版的排列組合
所以程式結構,跟上面幾樓的範例很像,只是沒那麼複雜

以下是簡單改寫後的範例









'使用方式,a欄填長度,b欄填各長度總數量,c2格填單個材料長度

Sub find_combin_Length()

Dim Data() As Double, Find_Answer() As Double, Temp_Length As Double, Min_Length As Double, ttt As Double
Dim i As Long, j As Long, k As Long, LastRow As Long, temp As Double, temp1 As Double, Total As Double, tempSum As Double
Dim Original_Length As Double, Original_Data As Range, Check As Boolean, Check1 As Double

If WorksheetFunction.CountA(Sheets("工作表1").Range("a:a")) = 1 Then Exit Sub

ttt = Timer
Sheets("工作表1").Range("e:s").ClearContents
Sheets("工作表1").Range("e:s").ColumnWidth = 13
LastRow = WorksheetFunction.CountA(Sheets("工作表1").Range("a:a"))
Set Original_Data = Sheets("工作表1").Range("A2").Resize(LastRow - 1, 2)
Original_Length = Sheets("工作表1").Range("c2")
ReDim Data(LastRow - 2, 1)

For i = 0 To UBound(Data, 1)
For j = 0 To UBound(Data, 2)
Data(i, j) = Original_Data.Cells(i + 1, j + 1)
Next j
Next i

For i = 0 To UBound(Data, 1) - 1
For j = i + 1 To UBound(Data, 1)
If Data(i, 0) < Data(j, 0) Then
temp = Data(j, 0)
temp1 = Data(j, 1)
Data(j, 0) = Data(i, 0)
Data(j, 1) = Data(i, 1)
Data(i, 0) = temp
Data(i, 1) = temp1
End If
Next j
Next i

If Data(0, 0) > Original_Length Then
MsgBox "長度超過原材料", vbOKOnly, "Error"
Exit Sub
End If

i = 0: k = 0: Check1 = 1: Min_Length = Data(UBound(Data), 0): Temp_Length = Original_Length

Do While Check1 > 0
Do While Temp_Length >= Min_Length
If Data(i, 0) <= Temp_Length And Data(i, 1) > 0 Then
Data(i, 1) = Data(i, 1) - 1
Temp_Length = Temp_Length - Data(i, 0)
ReDim Preserve Find_Answer(1, k)
Find_Answer(0, k) = Total + 1: Find_Answer(1, k) = Data(i, 0)
k = k + 1
Else
i = i + 1
End If

Check = True
For j = LBound(Data) To UBound(Data)
If Data(j, 1) > 0 Then
Min_Length = Data(j, 0)
Check = False
End If
Next j
If Check Then Exit Do
Loop

Temp_Length = Original_Length: Total = Total + 1

For j = UBound(Data) To LBound(Data) Step -1
If Data(j, 1) <> 0 Then
i = j
End If
Next j

Check1 = 0
For j = LBound(Data) To UBound(Data)
Check1 = Check1 + Data(j, 1)
Next j
Loop

j = 1: k = 0
For i = 0 To UBound(Find_Answer, 2)
If Find_Answer(0, i) <> j Then
Sheets("工作表1").Cells(1, j + 5) = "第" & j & "組" & vbNewLine & "合計=" & tempSum & vbNewLine & "尾料=" & Original_Length - tempSum 'debug
j = j + 1: k = 0: tempSum = 0
End If
Sheets("工作表1").Cells(k + 2, Find_Answer(0, i) + 5) = Find_Answer(1, i)
tempSum = tempSum + Find_Answer(1, i)
k = k + 1
Next i

Sheets("工作表1").Range("e1") = "最少需要" & vbNewLine & Total & "個"
Sheets("工作表1").Cells(1, j + 5) = "第" & j & "組" & vbNewLine & "合計=" & tempSum & vbNewLine & "尾料=" & Original_Length - tempSum 'debug
Sheets("工作表1").Range("e2") = Timer - ttt & "s" 'deubg

End Sub







[點擊下載]
我有看之前提到的用23個數字組成7143的範例
算這種問題用EXCEL內建規劃求解,選單純LP,大約十幾秒(據說收費版速度快幾十倍)

LINDO SYSTEM 的what's best 程式也可以去下載,按下去約一秒就解答
因為我的EXCEL有裝

所以我說句不中聽的話,還是用套裝就好,自已寫程式應該不會比專業寫程式的好



snare
另外,看了您的歷史發文,都是新聞、政治類的,怎麼突然出現在不曾來過的文書處理區,您是帳號本人嗎?
bluejay27
我只是最近在研究這個而已,哈哈,就東找西找
open solver 免錢的,也是一秒解



snare
您把姓名也貼出來了,不知您會不會介意,但我認為塗掉比較好喔。
bluejay27
塗掉了,感謝提醒
感謝大大的蓋樓以及持續追蹤,這巨集幫了我很大的忙
後面與高手切磋後的高速運算法也是非常驚豔,受益良多

不過我發現沒有解的時候,他會一直跑XD
所以我有寫個簡單的判斷式去處理他
另外有個小困擾,不知為啥常常數字少貼一個,有時候卻不會 (尤其解很少的時候)
snare
謝謝,能把不完整的教學文,自行改成實際應用,您也是相當厲害的高手
1.85
1.05

目標2.9
想問問為什麼這樣都跑不出來

先謝謝兩位大神了,
受益良多
snare
變數類型的關係,預設是算整數,小數點需修改程式碼,1樓有說明。
MinShane
第一時間有修改成Long 不過其他的小數點目標都跑得出來,不知道為什麼只有這個目標和組合有問題
MinShane wrote:
第一時間有修改成Long 不過其他的小數點目標都跑得出來,不知道為什麼只有這個目標和組合有問題


我開新檔,手動輸入數值,是可以正常算出來的



也許是您原始數值、excel設定、儲存格格式…等等的設定,剛好觸發了excel特有的問題
就是加總會誤差0.1、0.0001,您看到是2.9,excel實際上也許是2.8999
所以此組合條件不成立

微軟官方說明
https://learn.microsoft.com/zh-tw/office/troubleshoot/excel/floating-point-arithmetic-inaccurate-result

想看更多奇怪的例子,請google
"excel 小數點 加總 錯誤值 差 0.1"
MinShane
應該是這個原因 感謝 找了很久的原因找不到= =
snare wrote:
ren1244的教學


又是我!
請問30樓ren1244大大提供的下載檔案是可以支援有小數位嗎?
我試了一下,就出現下列版面



按偵錯,又出現以下版面,請問怎樣解決呢?謝!




我用的例子,就是以上圖中的數字,目標值是 39,897.49

---------------------------------------------------------
p.s. 之前一直是用樓主的程式,雖然不能支援較多數值,但如果不超過A-Z的話,還是可以運作的。
但最近樓主這程式不能運作,不知為何,即使我將組合加到目標數值,還是沒有運作,如下圖:

pretty_woman wrote:
即使我將組合加到目標數值,還是沒有運作


您的問題和36樓一樣,中招了
格子內看到的數值,和vba看到的是不一樣的

以我的程式碼來說
tempsum=39897.49
target=39897.49

tempsum=target,tempsum-target=0 才對

但是小數點的問題,造成vba加總的結果不同





如果您的資料很容易出現這種問題
最簡單的方式,就是在vba中限制小數點的數量












以您圖片中,11樓的範例,程式碼修改方式如下
把這行
If tempsum = target Then

改成如下
If Round(tempsum, 2) = Round(target, 2) Then



round(數值,2),是指小數點,2位數
也可用 WorksheetFunction.Round(數值,2) 代替


但要注意的是
vba的round()

excel儲存格用的函數=round()、=roundup、=rounddown()
計算出來的結果是不一樣的,通常是用round就行

不同之處,請參考微軟官方說明
https://learn.microsoft.com/zh-tw/office/vba/language/reference/user-interface-help/round-function
snare wrote:
tempsum


非常感謝!已照你的指示更改了程式,但你會知道如何更改ren1244大大的程式嗎?因我將可能有超過26個有小數點的數值需要運算,希望可以把運算速度加快一點點!
pretty_woman wrote:
如何更改ren1244大大的程式


一樣是小數點計算的問題,如果您資料不是手動輸入
常常用copy & paste ,就會莫明奇妙中招
詳細請到36樓,看微軟的官方說明







修改方式一樣,vba中限制小數點的個數
把這行
sumVal(stackIdx) = sumVal(stackIdx - 1) + data(0, idx)

改成這樣,暫訂小數2位
sumVal(stackIdx) = Round(sumVal(stackIdx - 1), 2) + Round(data(0, idx), 2)




另外發現一個bug,1解時輸出會少1個,
原來在2022-11-17 22:31(30樓),就有人提醒,只是他用“留言”
系統沒提示,因為不會沒事回頭看以前文章,我又漏看
所以沒發現到,不好意思,現在才處理








修改方式如下
把這行
Range("f2").Resize(nSolutions, maxLen) = outputData

改成
Range("f2").Resize(nSolutions, maxLen + 1) = outputData

文章分享
評分
評分
複製連結

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