Skip to content

Commit

Permalink
Merge pull request #3 from bavix/develop
Browse files Browse the repository at this point in the history
Optimize bp3
  • Loading branch information
rez1dent3 committed Aug 7, 2023
2 parents ec4b625 + 7b0d233 commit 835ee80
Show file tree
Hide file tree
Showing 6 changed files with 212 additions and 107 deletions.
5 changes: 5 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,8 @@ linters-settings:
allow:
- $gostd
- github.com
issues:
exclude-rules:
- path: (.+)_test.go
linters:
- dupl
43 changes: 23 additions & 20 deletions box.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
package boxpacker3

import (
"golang.org/x/exp/slices"
)

type Box struct {
id string
width float64
height float64
depth float64
maxWeight float64
volume float64
items []*Item
id string
width float64
height float64
depth float64
maxWeight float64
volume float64
items []*Item

maxLength float64
itemsVolume float64
itemsWeight float64
}
Expand All @@ -19,7 +25,7 @@ func (bs boxSlice) Len() int {
}

func (bs boxSlice) Less(i, j int) bool {
return bs[i].GetVolume() < bs[j].GetVolume()
return bs[i].volume < bs[j].volume
}

func (bs boxSlice) Swap(i, j int) {
Expand All @@ -34,6 +40,7 @@ func NewBox(id string, w, h, d, mw float64) *Box {
height: h,
depth: d,
maxWeight: mw,
maxLength: slices.Max([]float64{w, h, d}),
volume: w * h * d,
items: nil,
}
Expand Down Expand Up @@ -80,38 +87,34 @@ func (b *Box) PutItem(item *Item, p Pivot) bool {
item.rotationType = rt
d := item.GetDimension()

if b.GetWidth() < p[WidthAxis]+d[WidthAxis] || b.GetHeight() < p[HeightAxis]+d[HeightAxis] || b.GetDepth() < p[DepthAxis]+d[DepthAxis] {
if b.width < p[WidthAxis]+d[WidthAxis] || b.height < p[HeightAxis]+d[HeightAxis] || b.depth < p[DepthAxis]+d[DepthAxis] {
continue
}

fit = true

for _, ib := range b.items {
if ib.Intersect(item) {
fit = false

break
return false
}
}

if fit {
b.insert(item)

return fit
break
}

break
}

return fit
}

func (b *Box) canTryToPlace(item *Item) bool {
if b.itemsVolume+item.GetVolume() > b.GetVolume() {
if b.itemsVolume+item.volume > b.volume {
return false
}

if b.itemsWeight+item.GetWeight() > b.GetMaxWeight() {
if b.itemsWeight+item.weight > b.maxWeight {
return false
}

Expand All @@ -120,12 +123,12 @@ func (b *Box) canTryToPlace(item *Item) bool {

func (b *Box) insert(item *Item) {
b.items = append(b.items, item)
b.itemsVolume += item.GetVolume()
b.itemsWeight += item.GetWeight()
b.itemsVolume += item.volume
b.itemsWeight += item.weight
}

func (b *Box) purge() {
b.items = []*Item{}
b.items = b.items[:0] // keep allocated memory
b.itemsVolume = 0
b.itemsWeight = 0
}
4 changes: 3 additions & 1 deletion copyptr.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ func copyPtr[T any](original *T) *T {

func copySlicePtr[T any](data []*T) []*T {
result := make([]*T, len(data))

for i := range data {
result[i] = copyPtr(data[i])
val := *data[i]
result[i] = &val
}

return result
Expand Down
48 changes: 26 additions & 22 deletions item.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,19 @@ package boxpacker3

import (
"math"

"golang.org/x/exp/slices"
)

type Item struct {
id string
width float64
height float64
depth float64
weight float64
volume float64
id string
width float64
height float64
depth float64
weight float64
volume float64

maxLength float64
rotationType RotationType
position Pivot
}
Expand All @@ -32,12 +36,13 @@ func (it itemSlice) Swap(i, j int) {
func NewItem(id string, w, h, d, wg float64) *Item {
//nolint:exhaustruct
return &Item{
id: id,
width: w,
height: h,
depth: d,
weight: wg,
volume: w * h * d,
id: id,
width: w,
height: h,
depth: d,
weight: wg,
volume: w * h * d,
maxLength: slices.Max([]float64{w, h, d}),
}
}

Expand Down Expand Up @@ -69,24 +74,23 @@ func (i *Item) GetPosition() Pivot {
return i.position
}

//nolint:nonamedreturns
func (i *Item) GetDimension() (d Dimension) {
func (i *Item) GetDimension() Dimension {
switch i.rotationType {
case RotationTypeWhd:
d = Dimension{i.GetWidth(), i.GetHeight(), i.GetDepth()}
return Dimension{i.GetWidth(), i.GetHeight(), i.GetDepth()}
case RotationTypeHwd:
d = Dimension{i.GetHeight(), i.GetWidth(), i.GetDepth()}
return Dimension{i.GetHeight(), i.GetWidth(), i.GetDepth()}
case RotationTypeHdw:
d = Dimension{i.GetHeight(), i.GetDepth(), i.GetWidth()}
return Dimension{i.GetHeight(), i.GetDepth(), i.GetWidth()}
case RotationTypeDhw:
d = Dimension{i.GetDepth(), i.GetHeight(), i.GetWidth()}
return Dimension{i.GetDepth(), i.GetHeight(), i.GetWidth()}
case RotationTypeDwh:
d = Dimension{i.GetDepth(), i.GetWidth(), i.GetHeight()}
return Dimension{i.GetDepth(), i.GetWidth(), i.GetHeight()}
case RotationTypeWdh:
d = Dimension{i.GetWidth(), i.GetDepth(), i.GetHeight()}
return Dimension{i.GetWidth(), i.GetDepth(), i.GetHeight()}
default: // RotationTypeWhd
return Dimension{i.GetWidth(), i.GetHeight(), i.GetDepth()}
}

return
}

// Intersect Tests for intersections between the i element and the it element.
Expand Down
98 changes: 46 additions & 52 deletions packer.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,20 +25,18 @@ func (p *Packer) Pack(inputBoxes []*Box, inputItems []*Item) (*Result, error) {
sort.Sort(items)

result := &Result{
UnfitItems: make(itemSlice, 0, len(items)),
UnfitItems: nil,
Boxes: p.preferredSort(boxes, items),
}

for i := 0; len(items) > 0; i++ {
box := p.findRightBox(result.Boxes, items[0])
if box == nil {
result.UnfitItems = append(result.UnfitItems, items[0])
items = items[1:]

continue
for _, box := range result.Boxes {
if items = p.packToBox(box, items); len(items) == 0 {
break
}
}

items = p.packToBox(result.Boxes, box, items)
if len(items) > 0 {
result.UnfitItems = append(result.UnfitItems, items...)
}

return result, nil
Expand All @@ -47,17 +45,21 @@ func (p *Packer) Pack(inputBoxes []*Box, inputItems []*Item) (*Result, error) {
func (p *Packer) preferredSort(boxes boxSlice, items itemSlice) boxSlice {
volume := 0.
weight := 0.
maxLength := 0.

for _, item := range items {
volume += item.GetVolume()
weight += item.GetWeight()

// optimize
if maxLength < item.maxLength {
maxLength = item.maxLength
}
}

for i, b := range boxes {
if b.GetVolume() >= volume && b.GetMaxWeight() >= weight {
boxes = append(boxSlice{b}, slices.Delete(boxes, i, i+1)...)

break
if b.volume >= volume && b.maxWeight >= weight && b.maxLength >= maxLength {
return append(boxSlice{b}, slices.Delete(boxes, i, i)...)
}
}

Expand All @@ -67,26 +69,36 @@ func (p *Packer) preferredSort(boxes boxSlice, items itemSlice) boxSlice {
// packToBox Packs goods in a box b. Returns unpackaged goods.
//
//nolint:cyclop,gocognit,funlen
func (p *Packer) packToBox(boxes []*Box, b *Box, items []*Item) []*Item {
func (p *Packer) packToBox(b *Box, items []*Item) []*Item {
var fitted bool

unpacked := make([]*Item, 0, len(items))
pv := Pivot{}

if b.items == nil && b.PutItem(items[0], pv) {
items = items[1:]
}

// Packing unpackaged goods.
for _, i := range items {
fitted = false

// Trying anchor points for the box that don't intersect with existing items in the box.
for pt := WidthAxis; pt <= DepthAxis && !fitted; pt++ {
for _, ib := range b.items {
for j := range b.items {
dimension := b.items[j].GetDimension()

for pt := WidthAxis; pt <= DepthAxis && !fitted; pt++ {
pv[WidthAxis] = b.items[j].position[WidthAxis]
pv[HeightAxis] = b.items[j].position[HeightAxis]
pv[DepthAxis] = b.items[j].position[DepthAxis]

switch pt {
case WidthAxis:
pv = Pivot{ib.position[WidthAxis] + ib.GetWidth(), ib.position[HeightAxis], ib.position[DepthAxis]}
pv[WidthAxis] += +dimension[WidthAxis]
case HeightAxis:
pv = Pivot{ib.position[WidthAxis], ib.position[HeightAxis] + ib.GetHeight(), ib.position[DepthAxis]}
pv[HeightAxis] += dimension[HeightAxis]
case DepthAxis:
pv = Pivot{ib.position[WidthAxis], ib.position[HeightAxis], ib.position[DepthAxis] + ib.GetDepth()}
pv[DepthAxis] += dimension[DepthAxis]
}

if b.PutItem(i, pv) {
Expand All @@ -105,66 +117,48 @@ func (p *Packer) packToBox(boxes []*Box, b *Box, items []*Item) []*Item {

if b.PutItem(i, Pivot{}) {
total := len(copyItems)
iter := 0
itemsFit := 0

for k := 0; k < total && iter < total; k++ {
for k := 0; k < total && itemsFit < total; k++ {
// Trying anchor points for the box that don't intersect with existing items in the box.
for pt := WidthAxis; pt <= DepthAxis && iter < total; pt++ {
for _, ib := range b.items {
for j := len(b.items) - 1; j >= 0; j-- {
dimension := b.items[j].GetDimension()

for pt := WidthAxis; pt <= DepthAxis && k < total; pt++ {
pv[WidthAxis] = b.items[j].position[WidthAxis]
pv[HeightAxis] = b.items[j].position[HeightAxis]
pv[DepthAxis] = b.items[j].position[DepthAxis]

switch pt {
case WidthAxis:
pv = Pivot{ib.position[WidthAxis] + ib.GetWidth(), ib.position[HeightAxis], ib.position[DepthAxis]}
pv[WidthAxis] += +dimension[WidthAxis]
case HeightAxis:
pv = Pivot{ib.position[WidthAxis], ib.position[HeightAxis] + ib.GetHeight(), ib.position[DepthAxis]}
pv[HeightAxis] += dimension[HeightAxis]
case DepthAxis:
pv = Pivot{ib.position[WidthAxis], ib.position[HeightAxis], ib.position[DepthAxis] + ib.GetDepth()}
pv[DepthAxis] += dimension[DepthAxis]
}

if b.PutItem(copyItems[k], pv) {
iter++
itemsFit++

break
}
}
}
}

fitted = iter == total
fitted = itemsFit == total
}

if !fitted {
*b = *backup
}
}

for lb := p.findBiggerBox(boxes, b); lb != nil && !fitted; lb = p.findBiggerBox(boxes, lb) {
fitted = len(p.packToBox(boxes, lb, itemSlice{i})) == 0
}

if !fitted {
unpacked = append(unpacked, i)
}
}

return unpacked
}

func (p *Packer) findBiggerBox(boxes []*Box, box *Box) *Box {
for _, b := range boxes {
if b.volume > box.volume {
return b
}
}

return nil
}

func (p *Packer) findRightBox(boxes []*Box, item *Item) *Box {
for _, b := range boxes {
if b.volume >= item.volume {
return b
}
}

return nil
}
Loading

0 comments on commit 835ee80

Please sign in to comment.