|
This class can perform a one dimensional bin packing. It could be
used to: Sort pakages of a certain weight into containers that can
carry a given weight, cut 2x4s of a certain length into the required
pieces, fill floppy disks with a list of files...
A mix between standard Best Fit and Worst Fit algorithms is used.
Data must be entered and sorted descending before calling the main
PackBins Sub.
|
|
![]() |
|
|
Time Complexity (TC) of this function where
N = # of items If the average size of an item is 1/2 bin size then TC = N * N/2 or more generally: TC = N * N * Save% (where S is the percentage of average item size relative to bin capacity) So if average item size doesn't change doubling the number of items quadruples time spent. Using the second test time above would mean 5 million items would take 17.4 days to sort (5mil/5000 > 10002 * 1.5sec). However, we could assume that not much is gained by considering all items together. You could instead break it down to 5000 reps of the 1000 item problem which would give 0.06s * 5000 = 5min. You might save the results of each repetition, but remove the items in the last bin if not near full and return them to the items remaining to be sorted. Also note that as S is decreased the number of bins needed will approach the optimum. And as S is increased the assumption above is less true; it becomes more important to check as many items as possible to reduce the number of bins needed. I lack the math skills to prove it, but it's not difficult to see that if you have 1000 items with values ranging from 0 to 1000 and the bin capacity is 1000, there will be many bins with a large first item that won't have a near perfect match which will result in extra bins needed. So given a maximum item size Smax there should be a point at which checking more items causes a huge time penalty without doing much to optimize the number of bins needed: Iopt = Smax * M (where if Save% is large M > 1). One last note: It is perfectly possible that the number of bins needed far excedes the predicted optimum without implying that the sort did a bad job. Consider a problem where bin capacity is 10 and you have 4 items of size 6. 28/10 predicts that 3 bins will be needed while it is clear that 4 bins are needed. |
|
| Main function of class module |
Public Function PackBins()
Dim i As Long, j As Long, tot As Long
Dim iBestBin As Long, iWorstBin As Long, qBestBin As Long, qWorstBin As Long, x As Long
Dim iSize As Long '=m_Items(i).Size
Dim prevUbound As Long
pTightenPackageList
If UBound(m_Items) = 0 Then '1 item
ReDim m_Bins(m_iUItem To m_iUItem) 'so BinsNeeded will give correct val
Exit Function
End If
If m_BinReasonablyFull = 0 Then m_BinReasonablyFull = 0.95 * m_BinCapacity
For i = 0 To UBound(m_Items)
If m_Items(i).size <= m_BinCapacity Then tot = tot + m_Items(i).size
Next
'start with one less than min bins necessary to hold all items
'when remainder of tot / m_BinCapacity < 0.5
ReDim m_Bins(Int(tot / m_BinCapacity) + CLng((tot Mod m_BinCapacity) < m_BinCapacity / 2))
Debug.Print FormatNumber(tot / m_BinCapacity, 3) & "(i.e. " & Int(tot / m_BinCapacity) + IIf(tot Mod m_BinCapacity, 1, 0) & ") bins optimum - though not necessarily possible"
'place all items over 1/2 capacity in their own bin
qBestBin = Int(m_BinCapacity / 2)
For i = 0 To UBound(m_Items)
iSize = m_Items(i).size
If iSize > qBestBin Then
If iSize <= m_BinCapacity Then
m_Items(i).lBin = i
If i > UBound(m_Bins) Then pNewBin
m_Bins(i) = iSize 'mark bin usage
Else
m_Items(i).lBin = -1 'oversized
End If
Else
Exit For ' <= 1/2 bin capacity
End If
Next
'place the rest of the items
For i = i To UBound(m_Items)
'loop until a bin has been found
m_Items(i).lBin = -1 'no bin chosen yet
iBestBin = -1 'best bin index and flag that best bin found
iWorstBin = -1 'worst bin index and flag that fits in bin
qBestBin = m_BinReasonablyFull - 1 'a best bin has to be > this
qWorstBin = m_BinCapacity + 1
iSize = m_Items(i).size
j = 0 'current bin to check
Do
x = m_Bins(j) + iSize
'if fits in bin
If x <= m_BinCapacity Then
'is it most full bin
If x > qBestBin Then 'NOW qBestBin always= m_BinReasonablyFull-1
m_Items(i).lBin = j
m_Bins(j) = x ' m_Bins(j) + iSize
Exit Do
End If
'is it least full bin
If x < qWorstBin Then
qWorstBin = x
iWorstBin = j
End If
End If
'if current bin is empty or last bin and don't need new one it's time to decide where to place item
If (m_Bins(j) = 0) Or (j = UBound(m_Bins)) Then
If iWorstBin >= 0 Then
m_Items(i).lBin = iWorstBin
m_Bins(iWorstBin) = m_Bins(iWorstBin) + iSize
Exit Do
Else 'didn't fit - place in the empty bin or in new bin
If m_Bins(j) <> 0 Then
'we need a new bin (rare since we start with min bins
pNewBin
j = j + 1
End If
m_Items(i).lBin = j
m_Bins(j) = m_Bins(j) + iSize
Exit Do
End If
End If
'try next bin
j = j + 1
Loop 'While m_Items(i).lBin = -1 'now exit do takes care 15%faster in IDE
Next
'--------------------------------------------------------------
If m_Bins(UBound(m_Bins)) = 0 And UBound(m_Bins) > 0 Then
ReDim Preserve m_Bins(UBound(m_Bins) - 1)
End If
'try and place last items as 1st fit
'keep last bin as empty as possible
x = 0 ': j = 0
i = 0
Do 'For i = 0 To UBound(m_Items)
If m_Items(i).lBin = UBound(m_Bins) Then
If x Then
iSize = m_Items(i).size
For j = 0 To UBound(m_Bins) - 1
If m_Bins(j) + iSize <= m_BinCapacity Then
m_Bins(j) = m_Bins(j) + iSize
m_Bins(UBound(m_Bins)) = m_Bins(UBound(m_Bins)) - iSize
m_Items(i).lBin = j
Debug.Print "moved an item out of last bin"
'leave if only the first item remains in last bin
If m_Bins(UBound(m_Bins)) = x Then Exit Do
Exit For
End If
Next
Else
x = m_Items(i).size 'flag that we found and skipped first (largest) item in last bin
End If
End If
i = i + 1
Loop While i <= UBound(m_Items) 'Next
' SortPackagesByBin 'let user call this so we can see how binsort was executed
Debug.Print
End Function