Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

godoc #15

Merged
merged 8 commits into from
Jul 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,8 @@ updates:
schedule:
interval: daily
open-pull-requests-limit: 10
- package-ecosystem: github-actions
directory: "/"
schedule:
interval: daily
open-pull-requests-limit: 10
1 change: 1 addition & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,5 @@ issues:
exclude-rules:
- path: (.+)_test.go
linters:
- gochecknoglobals
- dupl
17 changes: 16 additions & 1 deletion bench_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,25 @@ import (
"github.com/bavix/boxpacker3"
)

// BenchmarkPacker benchmarks the Pack method of the Packer type.
//
// This benchmark uses a list of 100 random items and the default box list.
// The benchmark reports the number of allocations and resets the timer before
// the loop.
//
// The loop iterates over the items and packs them into boxes.
func BenchmarkPacker(b *testing.B) {
// Create a slice of 100 random items
items := make([]*boxpacker3.Item, 0, 100)

for range 100 {
for range cap(items) {
// Generate random dimensions for the item
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))

// Create a new item with the random dimensions
items = append(items, boxpacker3.NewItem(
uuid.New().String(),
float64(w.Int64()),
Expand All @@ -28,11 +38,16 @@ func BenchmarkPacker(b *testing.B) {
))
}

// Create a new box list with the default boxes
boxes := NewDefaultBoxList()
// Create a new packer
packer := boxpacker3.NewPacker()

// Report allocations and reset the timer
b.ReportAllocs()
b.ResetTimer()

// Iterate over the items and pack them into boxes
for i := 0; i < b.N; i++ {
_ = packer.Pack(boxes, items)
}
Expand Down
166 changes: 140 additions & 26 deletions box.go
Original file line number Diff line number Diff line change
@@ -1,37 +1,72 @@
package boxpacker3

import (
"slices"
)

// Box represents a box that can hold items.
//
// It has fields for the box's dimensions and maximum weight.
// It also has fields for tracking the box's current items and their volume and weight.
type Box struct {
id string
width float64
height float64
depth float64
// id is the box's unique identifier.
id string

// width is the box's width.
width float64

// height is the box's height.
height float64

// depth is the box's depth.
depth float64

// maxWeight is the maximum weight the box can hold.
maxWeight float64
volume float64
items []*Item

maxLength float64
// volume is the box's volume (width * height * depth).
volume float64

// items is a slice of items currently in the box.
items []*Item

// maxLength is the length of the box's longest side.
maxLength float64

// itemsVolume is the total volume of the items in the box.
itemsVolume float64

// itemsWeight is the total weight of the items in the box.
itemsWeight float64
}

// boxSlice is a slice of boxes.
//
// It implements the sort.Interface by defining Len, Less and Swap methods.
type boxSlice []*Box

// Len returns the length of the boxSlice.
func (bs boxSlice) Len() int {
return len(bs)
}

// Less compares two boxes by volume.
func (bs boxSlice) Less(i, j int) bool {
return bs[i].volume < bs[j].volume
}

// Swap swaps two boxes in the boxSlice.
func (bs boxSlice) Swap(i, j int) {
bs[i], bs[j] = bs[j], bs[i]
}

// NewBox creates a new Box with the given id, dimensions, and maximum weight.
//
// Parameters:
// - id: a unique identifier for the box.
// - w: the width of the box.
// - h: the height of the box.
// - d: the depth of the box.
// - mw: the maximum weight the box can hold.
//
// Returns:
// - A pointer to the newly created Box.
func NewBox(id string, w, h, d, mw float64) *Box {
//nolint:exhaustruct
return &Box{
Expand All @@ -40,82 +75,161 @@ func NewBox(id string, w, h, d, mw float64) *Box {
height: h,
depth: d,
maxWeight: mw,
maxLength: slices.Max([]float64{w, h, d}),
maxLength: max(w, h, d),
volume: w * h * d,
items: nil,
items: make([]*Item, 0, 1),
}
}

// GetID returns the unique identifier of the box.
func (b *Box) GetID() string {
return b.id
}

// GetWidth returns the width of the box.
func (b *Box) GetWidth() float64 {
return b.width
}

// GetHeight returns the height of the box.
func (b *Box) GetHeight() float64 {
return b.height
}

// GetDepth returns the depth of the box.
func (b *Box) GetDepth() float64 {
return b.depth
}

// GetVolume returns the volume of the box.
func (b *Box) GetVolume() float64 {
return b.volume
}

// GetMaxWeight returns the maximum weight the box can hold.
func (b *Box) GetMaxWeight() float64 {
return b.maxWeight
}

// GetItems returns a slice of pointers to the items currently in the box.
//
// The slice is a copy and not a reference to the original slice, so modifying
// the slice returned by this function will not affect the contents of the box.
func (b *Box) GetItems() []*Item {
return b.items
}

// PutItem Attempts to place an element at anchor point p of box b.
return append([]*Item(nil), b.items...)
}

// PutItem Attempts to place the given item at the specified anchor point within the box.
//
// Attempts to place the given item at the specified anchor point within the box.
//
// It tries to place the item at the given anchor point by iterating through each
// rotation type (Whd, Hwd, Hdw, Dhw, Dwh, Wdh) and checks if the item can be
// placed within the box without intersecting with any of the other items in the box.
// If the item can be placed, it inserts the item into the box and returns true.
// If the item cannot be placed, it returns false.
//
// Parameters:
// - item: The item to be placed in the box.
// - p: The anchor point at which to attempt placing the item within the box.
//
// Returns:
// - bool: True if the item was successfully placed within the box, false otherwise.
func (b *Box) PutItem(item *Item, p Pivot) bool {
// Check if the item can fit in the box based on volume and weight quotas.
if !b.canQuota(item) {
return false
}

// Set the item's position to the anchor point.
item.position = p

// Iterate through each rotation type to find a suitable placement.
for rt := RotationTypeWhd; rt <= RotationTypeWdh; rt++ {
// Set the item's rotation type to the current rotation type.
item.rotationType = rt
d := item.GetDimension()

if b.width < p[WidthAxis]+d[WidthAxis] || b.height < p[HeightAxis]+d[HeightAxis] || b.depth < p[DepthAxis]+d[DepthAxis] {
// Get the dimensions of the item in its current rotation type.
itemDimensions := item.GetDimension()

// Check if the box has enough dimensions to accommodate the item.
if b.width < p[WidthAxis]+itemDimensions[WidthAxis] ||
b.height < p[HeightAxis]+itemDimensions[HeightAxis] ||
b.depth < p[DepthAxis]+itemDimensions[DepthAxis] {
continue
}

for _, ib := range b.items {
if ib.Intersect(item) {
return false
}
// Check if the item intersects with any other items in the box.
if b.itemsIntersect(item) {
continue
}

// Insert the item into the box and return true.
b.insert(item)

return true
}

// If no suitable placement is found, return false.
return false
}

// itemsIntersect checks if any of the items in the box intersect with the given item.
// It iterates through each item in the box and calls the Intersect method on the item.
// If an intersection is found, it returns true.
// If no intersection is found, it returns false.
func (b *Box) itemsIntersect(item *Item) bool {
for _, ib := range b.items {
if ib.Intersect(item) {
return true
}
}

return false
}

// canQuota checks if the box can accommodate the given item based on both volume and weight quotas.
//
// It calls the canFitVolume and canFitWeight methods to check if the box has enough room for the
// item's volume and weight. If both conditions are true, it returns true. Otherwise, it returns false.
func (b *Box) canQuota(item *Item) bool {
return b.itemsVolume+item.volume <= b.volume && b.itemsWeight+item.weight <= b.maxWeight
return b.canFitVolume(item) && b.canFitWeight(item)
}

// canFitVolume checks if the box can accommodate the given item based on volume.
//
// It compares the sum of the item's volume and the current volume of items in the box
// to the box's total volume. If the sum is less than or equal to the box's total volume,
// it returns true. Otherwise, it returns false.
func (b *Box) canFitVolume(item *Item) bool {
return b.itemsVolume+item.volume <= b.volume
}

// canFitWeight checks if the box can accommodate the given item based on weight.
//
// It compares the sum of the item's weight and the current weight of items in the box
// to the box's maximum weight. If the sum is less than or equal to the box's maximum weight,
// it returns true. Otherwise, it returns false.
func (b *Box) canFitWeight(item *Item) bool {
return b.itemsWeight+item.weight <= b.maxWeight
}

// insert inserts an item into the box and updates the total volume and weight.
//
// It appends the item to the box's items slice and adds the item's volume and weight to the
// box's total volume and weight.
func (b *Box) insert(item *Item) {
b.items = append(b.items, item)
b.itemsVolume += item.volume
b.itemsWeight += item.weight
}

func (b *Box) purge() {
b.items = make([]*Item, 0, len(b.items))
// Reset clears the box and resets the volume and weight.
//
// It removes all items from the box by slicing the items slice to an empty slice.
// It sets the total volume and weight of items in the box to 0.
func (b *Box) Reset() {
b.items = b.items[:0]
b.itemsVolume = 0
b.itemsWeight = 0
}
20 changes: 16 additions & 4 deletions consts.go
Original file line number Diff line number Diff line change
@@ -1,25 +1,37 @@
package boxpacker3

// RotationType represents the type of rotation for an item.
type RotationType int

// RotationTypeWhd represents the rotation type where the width is the longest dimension.
const (
RotationTypeWhd RotationType = iota
// RotationTypeHwd represents the rotation type where the height is the longest dimension.
RotationTypeHwd
// RotationTypeHdw represents the rotation type where the depth is the longest dimension.
RotationTypeHdw
// RotationTypeDhw represents the rotation type where the depth is the longest dimension.
RotationTypeDhw
// RotationTypeDwh represents the rotation type where the width is the longest dimension.
RotationTypeDwh
// RotationTypeWdh represents the rotation type where the height is the longest dimension.
RotationTypeWdh
)

// Axis represents the axis of a dimension.
type Axis int

// WidthAxis represents the width axis.
const (
WidthAxis Axis = iota
// HeightAxis represents the height axis.
HeightAxis
// DepthAxis represents the depth axis.
DepthAxis
)

type (
Pivot [3]float64
Dimension [3]float64
)
// Pivot represents the position of an item within a box.
type Pivot [3]float64

// Dimension represents the dimensions of an item or a box.
type Dimension [3]float64
12 changes: 12 additions & 0 deletions copyptr.go
Original file line number Diff line number Diff line change
@@ -1,23 +1,35 @@
package boxpacker3

// CopyPtr creates a copy of a pointer.
//
// It takes a pointer to a generic type T as an argument and returns a new pointer
// to a copy of the original value. If the original pointer is nil, it returns nil.
func CopyPtr[T any](original *T) *T {
// If the original pointer is nil, return nil.
if original == nil {
return nil
}

// Create a copy of the value pointed to by the original pointer.
copyOfValue := *original

// Return a new pointer to the copied value.
return &copyOfValue
}

// CopySlicePtr creates a copy of a slice of pointers.
//
// It takes a slice of pointers as an argument and returns a new slice with the same
// elements, but with each element being a copy of the original.
func CopySlicePtr[T any](data []*T) []*T {
// Create a new slice with the same length as the original.
result := make([]*T, len(data))

// Iterate over the original slice and copy each element to the new slice.
for i, item := range data {
result[i] = CopyPtr(item)
}

// Return the new slice.
return result
}
Loading