diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml new file mode 100644 index 0000000..6b9abea --- /dev/null +++ b/.github/workflows/golangci-lint.yml @@ -0,0 +1,26 @@ +name: golangci-lint +on: + push: + branches: + - master + pull_request: +permissions: + contents: read +jobs: + golangci: + name: lint + runs-on: ubuntu-latest + strategy: + matrix: + go-version: [ '1.20' ] + steps: + - uses: actions/checkout@v3 + - name: Setup Go + uses: actions/setup-go@v4 + with: + go-version: ${{ matrix.go-version }} + cache: true + - name: golangci-lint + uses: golangci/golangci-lint-action@v3 + with: + version: v1.53.3 diff --git a/.github/workflows/unit.yml b/.github/workflows/unit.yml new file mode 100644 index 0000000..c0fe53a --- /dev/null +++ b/.github/workflows/unit.yml @@ -0,0 +1,30 @@ +name: gotest-unit +on: + push: + branches: + - master + pull_request: +permissions: + contents: read +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + go-version: [ '1.20' ] + steps: + - uses: actions/checkout@v3 + - name: Setup Go + uses: actions/setup-go@v4 + with: + go-version: ${{ matrix.go-version }} + cache: true + - name: Install dependencies + run: go get . + - name: Test with Go + run: go test -json > TestResults-${{ matrix.go-version }}.json + - name: Upload Go test results + uses: actions/upload-artifact@v3 + with: + name: Go-results-${{ matrix.go-version }} + path: TestResults-${{ matrix.go-version }}.json diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..25da098 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,32 @@ +run: + timeout: 1m +linters: + enable-all: true + disable: + # deprecated + - nosnakecase + - structcheck + - interfacer + - deadcode + - exhaustivestruct + - maligned + - ifshort + - varcheck + - golint + - scopelint + # not relevant + - varnamelen +linters-settings: + lll: + line-length: 160 + gci: + sections: + - Standard + - Default + - Prefix(github.com/bavix) + depguard: + rules: + main: + allow: + - $gostd + - github.com diff --git a/bench_test.go b/bench_test.go index f8630e8..fc89109 100644 --- a/bench_test.go +++ b/bench_test.go @@ -1,7 +1,7 @@ package boxpacker3_test import ( - rand2 "crypto/rand" + "crypto/rand" "math/big" "testing" @@ -11,13 +11,13 @@ import ( ) func BenchmarkPacker(b *testing.B) { - items := make(boxpacker3.ItemSlice, 0, 100) + items := make([]*boxpacker3.Item, 0, 100) for x := 0; x < 100; x++ { - w, _ := rand2.Int(rand2.Reader, big.NewInt(150)) - l, _ := rand2.Int(rand2.Reader, big.NewInt(150)) - h, _ := rand2.Int(rand2.Reader, big.NewInt(150)) - w2, _ := rand2.Int(rand2.Reader, big.NewInt(100)) + w, _ := rand.Int(rand.Reader, big.NewInt(150)) + l, _ := rand.Int(rand.Reader, big.NewInt(150)) + h, _ := rand.Int(rand.Reader, big.NewInt(150)) + w2, _ := rand.Int(rand.Reader, big.NewInt(100)) items = append(items, boxpacker3.NewItem( uuid.New().String(), diff --git a/box.go b/box.go index cbd706b..a1a420c 100644 --- a/box.go +++ b/box.go @@ -1,83 +1,83 @@ package boxpacker3 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 itemsVolume float64 itemsWeight float64 } -type BoxSlice []*Box +type boxSlice []*Box -func (bs BoxSlice) Len() int { +func (bs boxSlice) Len() int { return len(bs) } -func (bs BoxSlice) Less(i, j int) bool { +func (bs boxSlice) Less(i, j int) bool { return bs[i].GetVolume() < bs[j].GetVolume() } -func (bs BoxSlice) Swap(i, j int) { +func (bs boxSlice) Swap(i, j int) { bs[i], bs[j] = bs[j], bs[i] } func NewBox(id string, w, h, d, mw float64) *Box { + //nolint:exhaustruct return &Box{ - ID: id, - Width: w, - Height: h, - Depth: d, - MaxWeight: mw, - Volume: w * h * d, - Items: make([]*Item, 0), + id: id, + width: w, + height: h, + depth: d, + maxWeight: mw, + volume: w * h * d, + items: nil, } } func (b *Box) GetID() string { - return b.ID + return b.id } func (b *Box) GetWidth() float64 { - return b.Width + return b.width } func (b *Box) GetHeight() float64 { - return b.Height + return b.height } func (b *Box) GetDepth() float64 { - return b.Depth + return b.depth } func (b *Box) GetVolume() float64 { - return b.Volume + return b.volume } func (b *Box) GetMaxWeight() float64 { - return b.MaxWeight + return b.maxWeight } -// PutItem Пытается поместить элемент в опорную точку p коробки b. -func (b *Box) PutItem(item *Item, p Pivot) bool { - fit := false +func (b *Box) GetItems() []*Item { + return b.items +} - if b.itemsVolume+item.GetVolume() > b.GetVolume() { +// PutItem Attempts to place an element at anchor point p of box b. +func (b *Box) PutItem(item *Item, p Pivot) bool { + if !b.canTryToPlace(item) { return false } - if b.itemsWeight+item.GetWeight() > b.GetMaxWeight() { - return false - } + fit := false + item.position = p - item.Position = p for rt := RotationTypeWhd; rt <= RotationTypeWdh; rt++ { - item.RotationType = rt + 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] { @@ -86,7 +86,7 @@ func (b *Box) PutItem(item *Item, p Pivot) bool { fit = true - for _, ib := range b.Items { + for _, ib := range b.items { if ib.Intersect(item) { fit = false @@ -106,9 +106,26 @@ func (b *Box) PutItem(item *Item, p Pivot) bool { return fit } -func (b *Box) insert(item *Item) { - b.Items = append(b.Items, item) +func (b *Box) canTryToPlace(item *Item) bool { + if b.itemsVolume+item.GetVolume() > b.GetVolume() { + return false + } + + if b.itemsWeight+item.GetWeight() > b.GetMaxWeight() { + return false + } + return true +} + +func (b *Box) insert(item *Item) { + b.items = append(b.items, item) b.itemsVolume += item.GetVolume() b.itemsWeight += item.GetWeight() } + +func (b *Box) purge() { + b.items = []*Item{} + b.itemsVolume = 0 + b.itemsWeight = 0 +} diff --git a/clone_test.go b/clone_test.go deleted file mode 100644 index d5686ea..0000000 --- a/clone_test.go +++ /dev/null @@ -1,33 +0,0 @@ -package boxpacker3 - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestCopyPtr(t *testing.T) { - a := 1 - b := struct{ a int }{} - - require.Same(t, &a, &a) - require.Same(t, &b, &b) - require.NotSame(t, &a, copyPtr(&a)) - require.NotSame(t, &b, copyPtr(&b)) -} - -func TestCopySlicePtr(t *testing.T) { - a := struct{ a int }{} - b := struct{ a int }{} - c := []*struct{ a int }{&a, &b} - - d := make([]*struct{ a int }, len(c)) - copy(d, c) - - e := copySlicePtr(c) - - for i := range d { - require.Same(t, c[i], d[i]) - require.NotSame(t, c[i], e[i]) - } -} diff --git a/clone.go b/copyptr.go similarity index 100% rename from clone.go rename to copyptr.go diff --git a/go.mod b/go.mod index 85c0993..937aae4 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.20 require ( github.com/google/uuid v1.3.0 github.com/stretchr/testify v1.8.4 + golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b ) require ( diff --git a/go.sum b/go.sum index 8d8b455..2ffca3b 100644 --- a/go.sum +++ b/go.sum @@ -6,6 +6,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b h1:r+vk0EmXNmekl0S0BascoeeoHk/L7wmaW2QF90K+kYI= +golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/item.go b/item.go index 22883cf..c4d0981 100644 --- a/item.go +++ b/item.go @@ -5,69 +5,69 @@ import ( ) type Item struct { - ID string - Width float64 - Height float64 - Depth float64 - Weight float64 - Volume float64 - - RotationType RotationType - Position Pivot + id string + width float64 + height float64 + depth float64 + weight float64 + volume float64 + rotationType RotationType + position Pivot } -type ItemSlice []*Item +type itemSlice []*Item -func (is ItemSlice) Len() int { - return len(is) +func (it itemSlice) Len() int { + return len(it) } -func (is ItemSlice) Less(i, j int) bool { - return is[i].GetVolume() < is[j].GetVolume() +func (it itemSlice) Less(i, j int) bool { + return it[i].GetVolume() < it[j].GetVolume() } -func (is ItemSlice) Swap(i, j int) { - is[i], is[j] = is[j], is[i] +func (it itemSlice) Swap(i, j int) { + it[i], it[j] = it[j], it[i] } 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, } } func (i *Item) GetID() string { - return i.ID + return i.id } func (i *Item) GetWidth() float64 { - return i.Width + return i.width } func (i *Item) GetHeight() float64 { - return i.Height + return i.height } func (i *Item) GetDepth() float64 { - return i.Depth + return i.depth } func (i *Item) GetVolume() float64 { - return i.Volume + return i.volume } func (i *Item) GetWeight() float64 { - return i.Weight + return i.weight } //nolint:nonamedreturns func (i *Item) GetDimension() (d Dimension) { - switch i.RotationType { + switch i.rotationType { case RotationTypeWhd: d = Dimension{i.GetWidth(), i.GetHeight(), i.GetDepth()} case RotationTypeHwd: @@ -85,7 +85,7 @@ func (i *Item) GetDimension() (d Dimension) { return } -// Intersect Проверяет пересечения между элементом i и элементом it. +// Intersect Tests for intersections between the i element and the it element. func (i *Item) Intersect(it *Item) bool { d1 := i.GetDimension() d2 := it.GetDimension() @@ -95,12 +95,12 @@ func (i *Item) Intersect(it *Item) bool { rectIntersect(d1, d2, i, it, WidthAxis, DepthAxis) } -// rectIntersect Проверяет пересекаются ли два прямоугольника от осей x и y элементов i1 и i2. +// rectIntersect Checks if two rectangles intersect from the x and y axes of elements i1 and i2. func rectIntersect(d1, d2 Dimension, i1, i2 *Item, x, y Axis) bool { - cx1 := i1.Position[x] + d1[x]/2 //nolint:gomnd - cy1 := i1.Position[y] + d1[y]/2 //nolint:gomnd - cx2 := i2.Position[x] + d2[x]/2 //nolint:gomnd - cy2 := i2.Position[y] + d2[y]/2 //nolint:gomnd + cx1 := i1.position[x] + d1[x]/2 //nolint:gomnd + cy1 := i1.position[y] + d1[y]/2 //nolint:gomnd + cx2 := i2.position[x] + d2[x]/2 //nolint:gomnd + cy2 := i2.position[y] + d2[y]/2 //nolint:gomnd ix := math.Max(cx1, cx2) - math.Min(cx1, cx2) iy := math.Max(cy1, cy2) - math.Min(cy1, cy2) diff --git a/packer.go b/packer.go index 9928074..2177119 100644 --- a/packer.go +++ b/packer.go @@ -2,13 +2,15 @@ package boxpacker3 import ( "sort" + + "golang.org/x/exp/slices" ) type Packer struct{} type Result struct { - UnfitItems ItemSlice - Boxes BoxSlice + UnfitItems itemSlice + Boxes boxSlice } func NewPacker() *Packer { @@ -16,20 +18,19 @@ func NewPacker() *Packer { } func (p *Packer) Pack(inputBoxes []*Box, inputItems []*Item) (*Result, error) { - boxes := BoxSlice(copySlicePtr(inputBoxes)) - items := ItemSlice(copySlicePtr(inputItems)) + boxes := boxSlice(copySlicePtr(inputBoxes)) + items := itemSlice(copySlicePtr(inputItems)) sort.Sort(boxes) sort.Sort(items) result := &Result{ - UnfitItems: make(ItemSlice, 0, len(items)), + UnfitItems: make(itemSlice, 0, len(items)), Boxes: p.preferredSort(boxes, items), } - maxIter := len(items) - for i := 0; len(items) > 0 && i < maxIter; i++ { - box := p.FindFittedBox(result.Boxes, items[0]) + 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:] @@ -43,7 +44,7 @@ func (p *Packer) Pack(inputBoxes []*Box, inputItems []*Item) (*Result, error) { return result, nil } -func (p *Packer) preferredSort(boxes BoxSlice, items ItemSlice) BoxSlice { +func (p *Packer) preferredSort(boxes boxSlice, items itemSlice) boxSlice { volume := 0. weight := 0. @@ -52,48 +53,40 @@ func (p *Packer) preferredSort(boxes BoxSlice, items ItemSlice) BoxSlice { weight += item.GetWeight() } - preferredBoxes := make(BoxSlice, 0, len(boxes)) - otherBoxes := make(BoxSlice, 0, len(boxes)) - - for _, b := range boxes { + for i, b := range boxes { if b.GetVolume() >= volume && b.GetMaxWeight() >= weight { - preferredBoxes = append(preferredBoxes, b) - } else { - otherBoxes = append(otherBoxes, b) + boxes = append(boxSlice{b}, slices.Delete(boxes, i, i+1)...) + + break } } - return append(preferredBoxes, otherBoxes...) + return boxes } -// packToBox Упаковывает товары в коробку b. Возвращает не упакованные товары. +// packToBox Packs goods in a box b. Returns unpackaged goods. // -//nolint:cyclop,nonamedreturns,gocognit,funlen -func (p *Packer) packToBox(boxes []*Box, b *Box, items []*Item) (unpacked []*Item) { - if !b.PutItem(items[0], Pivot{0, 0, 0}) { - if b2 := p.getBiggerBoxThan(boxes, b); b2 != nil { - return p.packToBox(boxes, b2, items) - } +//nolint:cyclop,gocognit,funlen +func (p *Packer) packToBox(boxes []*Box, b *Box, items []*Item) []*Item { + var fitted bool - return items - } + unpacked := make([]*Item, 0, len(items)) + pv := Pivot{} - // Упаковываем неупакованные товары. - for _, i := range items[1:] { - var fitted bool + // 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 { - var pv Pivot - + for _, ib := range b.items { switch pt { case WidthAxis: - pv = Pivot{ib.Position[WidthAxis] + ib.GetWidth(), ib.Position[HeightAxis], ib.Position[DepthAxis]} + pv = Pivot{ib.position[WidthAxis] + ib.GetWidth(), ib.position[HeightAxis], ib.position[DepthAxis]} case HeightAxis: - pv = Pivot{ib.Position[WidthAxis], ib.Position[HeightAxis] + ib.GetHeight(), ib.Position[DepthAxis]} + pv = Pivot{ib.position[WidthAxis], ib.position[HeightAxis] + ib.GetHeight(), ib.position[DepthAxis]} case DepthAxis: - pv = Pivot{ib.Position[WidthAxis], ib.Position[HeightAxis], ib.Position[DepthAxis] + ib.GetDepth()} + pv = Pivot{ib.position[WidthAxis], ib.position[HeightAxis], ib.position[DepthAxis] + ib.GetDepth()} } if b.PutItem(i, pv) { @@ -104,42 +97,31 @@ func (p *Packer) packToBox(boxes []*Box, b *Box, items []*Item) (unpacked []*Ite } } - //nolint:nestif if !fitted { - forRevert := copySlicePtr(b.Items) - itemSlice := copySlicePtr(b.Items) - bkitemsWeight := b.itemsWeight - bkitemsVolume := b.itemsVolume - b.Items = []*Item{} - b.itemsWeight = 0 - b.itemsVolume = 0 - - if b.PutItem(i, Pivot{0, 0, 0}) { - cnt := len(itemSlice) - for k := 0; k < 100 && cnt > 0; k++ { - j := itemSlice[0] - - // Пробуем опорные точки для коробки, которые не пересекаются с существующими товарами в коробке. - for pt := WidthAxis; pt <= DepthAxis && cnt > 0; pt++ { - for _, ib := range b.Items { - if j == ib { - continue - } + backup := copyPtr(b) + copyItems := copySlicePtr(b.items) + + b.purge() - var pv Pivot + if b.PutItem(i, Pivot{}) { + total := len(copyItems) + iter := 0 + for k := 0; k < total && iter < 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 { switch pt { case WidthAxis: - pv = Pivot{ib.Position[WidthAxis] + ib.GetWidth(), ib.Position[HeightAxis], ib.Position[DepthAxis]} + pv = Pivot{ib.position[WidthAxis] + ib.GetWidth(), ib.position[HeightAxis], ib.position[DepthAxis]} case HeightAxis: - pv = Pivot{ib.Position[WidthAxis], ib.Position[HeightAxis] + ib.GetHeight(), ib.Position[DepthAxis]} + pv = Pivot{ib.position[WidthAxis], ib.position[HeightAxis] + ib.GetHeight(), ib.position[DepthAxis]} case DepthAxis: - pv = Pivot{ib.Position[WidthAxis], ib.Position[HeightAxis], ib.Position[DepthAxis] + ib.GetDepth()} + pv = Pivot{ib.position[WidthAxis], ib.position[HeightAxis], ib.position[DepthAxis] + ib.GetDepth()} } - if b.PutItem(j, pv) { - itemSlice = itemSlice[1:] - cnt-- + if b.PutItem(copyItems[k], pv) { + iter++ break } @@ -147,61 +129,41 @@ func (p *Packer) packToBox(boxes []*Box, b *Box, items []*Item) (unpacked []*Ite } } - fitted = len(itemSlice) == 0 - - if !fitted { - b.Items = forRevert - b.itemsVolume = bkitemsVolume - b.itemsWeight = bkitemsWeight - } - } else { - b.Items = forRevert - b.itemsVolume = bkitemsVolume - b.itemsWeight = bkitemsWeight - } - - for b2 := p.getBiggerBoxThan(boxes, b); b2 != nil && !fitted; b2 = p.getBiggerBoxThan(boxes, b) { - if left := p.packToBox(boxes, b2, append(b2.Items, i)); len(left) == 0 { - b = b2 - fitted = true - } + fitted = iter == total } if !fitted { - unpacked = append(unpacked, i) + *b = *backup } } - } - return //nolint:nakedret -} + for lb := p.findBiggerBox(boxes, b); lb != nil && !fitted; lb = p.findBiggerBox(boxes, lb) { + fitted = len(p.packToBox(boxes, lb, itemSlice{i})) == 0 + } -func (p *Packer) getBiggerBoxThan(boxes []*Box, b *Box) *Box { - v := b.GetVolume() - for _, b2 := range boxes { - if b2.GetVolume() > v { - return b2 + if !fitted { + unpacked = append(unpacked, i) } } - return nil + return unpacked } -// FindFittedBox находит коробку для товара. -func (p *Packer) FindFittedBox(boxes []*Box, i *Item) *Box { +func (p *Packer) findBiggerBox(boxes []*Box, box *Box) *Box { for _, b := range boxes { - if !b.PutItem(i, Pivot{0, 0, 0}) { - continue + if b.volume > box.volume { + return b } + } - if len(b.Items) == 1 && b.Items[0] == i { - b.itemsVolume -= i.GetVolume() - b.itemsWeight -= i.GetWeight() + return nil +} - b.Items = []*Item{} +func (p *Packer) findRightBox(boxes []*Box, item *Item) *Box { + for _, b := range boxes { + if b.volume >= item.volume { + return b } - - return b } return nil diff --git a/packer_test.go b/packer_test.go index cbb9e91..42eb208 100644 --- a/packer_test.go +++ b/packer_test.go @@ -1,56 +1,67 @@ +//nolint:dupl package boxpacker3_test import ( "testing" - "github.com/bavix/boxpacker3" "github.com/google/uuid" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" + + "github.com/bavix/boxpacker3" ) const ( - // BoxTypeF "Тип Е", 220, 185, 50, 20000. + // BoxTypeF -- 220, 185, 50, 20000. BoxTypeF = "8ec81501-11a4-4b3f-9a52-7cd2f9c8370c" - // BoxTypeE "Тип Д", 165, 215, 100, 20000. + // BoxTypeE -- 165, 215, 100, 20000. BoxTypeE = "9c69baf8-1ca3-46a0-9fc2-6f15ad9fef9a" - // BoxTypeG "Тип Г", 265, 165, 190, 20000. + // BoxTypeG -- 265, 165, 190, 20000. BoxTypeG = "2c5279d3-48ad-451b-b673-f6d9be7fc6f6" - // BoxTypeC "Тип В", 425, 165, 190, 20000. + // BoxTypeC -- 425, 165, 190, 20000. BoxTypeC = "7f1cc68f-d554-4094-8734-c68df5c13154" - // BoxTypeB "Тип Б", 425, 265, 190, 20000. + // BoxTypeB -- 425, 265, 190, 20000. BoxTypeB = "76cede41-86bb-4487-bfb0-9513f032d53e" - // BoxTypeA "Тип А", 425, 265, 380, 20000. + // BoxTypeA -- 425, 265, 380, 20000. BoxTypeA = "8e10cebf-cee6-4136-b060-1587b993d083" - // BoxTypeStd "Стандартная", 530, 380, 265, 20000. + // BoxTypeStd -- 530, 380, 265, 20000. BoxTypeStd = "ba973206-aa64-493b-b37a-c53192cde8fd" - // BoxTypeNotStd1 "Не Стандартная 1", 1000, 500, 500, 20000. + // BoxTypeNotStd1 -- 1000, 500, 500, 20000. BoxTypeNotStd1 = "cb1ed5b8-7405-48c5-bfd0-d86f75c99261" - // BoxTypeNotStd2 "Не Стандартная 2", 1000, 1000, 1000, 20000. + // BoxTypeNotStd2 -- 1000, 1000, 1000, 20000. BoxTypeNotStd2 = "d91e2661-aebb-4a55-bfb5-4ff9c6e3c008" - // BoxTypeNotStd3 "Не Стандартная 3", 2000, 500, 500, 20000. + // BoxTypeNotStd3 -- 2000, 500, 500, 20000. BoxTypeNotStd3 = "a0ecd730-375a-4313-bbe8-820710606b3d" - // BoxTypeNotStd4 "Не Стандартная 4", 2000, 2000, 2000, 20000. + // BoxTypeNotStd4 -- 2000, 2000, 2000, 20000. BoxTypeNotStd4 = "6dff37f0-4dd1-4143-abdc-c19ab94f2e68" - // BoxTypeNotStd5 "Не Стандартная 5", 2500, 2500, 2500, 20000. + // BoxTypeNotStd5 -- 2500, 2500, 2500, 20000. BoxTypeNotStd5 = "abac6d59-b51f-4d62-a338-42aca7afe1cc" - // BoxTypeNotStd6 "Огромные размеры 6", 3000, 3000, 3000, 20000. + // BoxTypeNotStd6 -- 3000, 3000, 3000, 20000. BoxTypeNotStd6 = "981ffb30-a7b9-4d9e-820e-04de2145763e" ) -//nolint:gomnd +type PackerSuit struct { + suite.Suite +} + +func TestBoxPackerSuite(t *testing.T) { + t.Parallel() + + suite.Run(t, new(PackerSuit)) +} + func NewDefaultBoxList() []*boxpacker3.Box { return []*boxpacker3.Box{ boxpacker3.NewBox(BoxTypeF, 220, 185, 50, 20000), // 0 @@ -69,16 +80,6 @@ func NewDefaultBoxList() []*boxpacker3.Box { } } -type PackerSuit struct { - suite.Suite -} - -func TestBoxPackerSuite(t *testing.T) { - t.Parallel() - - suite.Run(t, new(PackerSuit)) -} - func (s *PackerSuit) TestMinBox() { t := s.T() t.Parallel() @@ -103,7 +104,7 @@ func (s *PackerSuit) TestMinBox() { require.Len(t, packResult.UnfitItems, 0) for i := 0; i < len(packResult.Boxes); i++ { - require.Len(t, packResult.Boxes[i].Items, checks[packResult.Boxes[i].ID]) + require.Len(t, packResult.Boxes[i].GetItems(), checks[packResult.Boxes[i].GetID()]) } } @@ -133,7 +134,7 @@ func (s *PackerSuit) TestRotate() { require.Len(t, packResult.UnfitItems, 0) for i := 0; i < len(packResult.Boxes); i++ { - require.Len(t, packResult.Boxes[i].Items, checks[packResult.Boxes[i].ID], packResult.Boxes[i].ID) + require.Len(t, packResult.Boxes[i].GetItems(), checks[packResult.Boxes[i].GetID()], packResult.Boxes[i].GetID()) } } @@ -163,7 +164,7 @@ func (s *PackerSuit) TestStd() { require.Len(t, packResult.UnfitItems, 0) for i := 0; i < len(packResult.Boxes); i++ { - require.Len(t, packResult.Boxes[i].Items, checks[packResult.Boxes[i].ID], packResult.Boxes[i].ID) + require.Len(t, packResult.Boxes[i].GetItems(), checks[packResult.Boxes[i].GetID()], packResult.Boxes[i].GetID()) } } @@ -204,6 +205,67 @@ func (s *PackerSuit) TestPacker_AllBoxes() { require.Len(t, packResult.UnfitItems, 0) for i := 0; i < len(packResult.Boxes); i++ { - require.Len(t, packResult.Boxes[i].Items, 1) + require.Len(t, packResult.Boxes[i].GetItems(), 1, packResult.Boxes[i].GetID()) + } +} + +func (s *PackerSuit) TestPacker_MinAndStd() { + t := s.T() + t.Parallel() + + packer := boxpacker3.NewPacker() + + boxes := NewDefaultBoxList() + reverse := make([]*boxpacker3.Box, len(boxes)) + + for i := range boxes { + reverse[i] = boxes[len(boxes)-1-i] + } + + items := []*boxpacker3.Item{ + // std + boxpacker3.NewItem(uuid.New().String(), 100, 380, 250, 2690), // 1 + boxpacker3.NewItem(uuid.New().String(), 380, 100, 250, 2690), // 2 + boxpacker3.NewItem(uuid.New().String(), 250, 380, 100, 2690), // 3 + boxpacker3.NewItem(uuid.New().String(), 100, 380, 250, 2690), // 4 + boxpacker3.NewItem(uuid.New().String(), 100, 380, 250, 2690), // 5 + + // min + boxpacker3.NewItem(uuid.New().String(), 220, 185, 50, 20000), // 6. F + boxpacker3.NewItem(uuid.New().String(), 165, 215, 100, 20000), // 7. E + boxpacker3.NewItem(uuid.New().String(), 265, 165, 190, 20000), // 8. G + boxpacker3.NewItem(uuid.New().String(), 425, 165, 190, 20000), // 9. C + boxpacker3.NewItem(uuid.New().String(), 425, 265, 190, 20000), // 10. B + boxpacker3.NewItem(uuid.New().String(), 425, 265, 380, 20000), // 11. A + boxpacker3.NewItem(uuid.New().String(), 530, 380, 265, 20000), // 12. Std + boxpacker3.NewItem(uuid.New().String(), 1000, 500, 500, 20000), // 13. NotStd1 + + // max + boxpacker3.NewItem(uuid.New().String(), 3000, 3000, 3000, 20000), // 14. NotStd6 + } + + packResult, err := packer.Pack(reverse, items) + require.NoError(t, err) + require.NotNil(t, packResult) + + checks := map[string]int{ + BoxTypeF: 1, // 1 + BoxTypeE: 1, // 2 + BoxTypeG: 1, // 3 + BoxTypeC: 1, // 4 + BoxTypeB: 1, // 5 + BoxTypeA: 3, // 8 + BoxTypeStd: 1, // 9 + BoxTypeNotStd1: 1, // 10 + BoxTypeNotStd2: 1, // 11 + BoxTypeNotStd3: 1, // 12 + BoxTypeNotStd4: 1, // 13 + BoxTypeNotStd6: 1, // 14 + } + + require.Len(t, packResult.UnfitItems, 0) + + for i := 0; i < len(packResult.Boxes); i++ { + require.Len(t, packResult.Boxes[i].GetItems(), checks[packResult.Boxes[i].GetID()], packResult.Boxes[i].GetID()) } }