Skip to content

Commit

Permalink
Merge pull request #15 from bavix/bxp-docs
Browse files Browse the repository at this point in the history
godoc
  • Loading branch information
rez1dent3 authored Jul 12, 2024
2 parents fe0a5dd + b87ffd6 commit 294ac72
Show file tree
Hide file tree
Showing 11 changed files with 712 additions and 185 deletions.
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

0 comments on commit 294ac72

Please sign in to comment.