~rbn/fit

daf41e823a85053852d823392280df90be3d999f — Ruben Schuller 1 year, 3 months ago f8753b4
change Item to be an interface
8 files changed, 73 insertions(+), 40 deletions(-)

M bin.go
M bin_test.go
M cmd/fit/input.go
M cmd/fit/main.go
M item.go
M pack.go
M select.go
M select_test.go
M bin.go => bin.go +8 -2
@@ 4,33 4,39 @@ import (
	"fmt"
)

//Bin is a collection of items requiring at most a given size.
type Bin struct {
	items    []Item
	size     int64
	capacity int64
}

//add a new item to the bin.
func (b *Bin) add(i Item) error {
	if !b.fits(i) {
		return fmt.Errorf("item doesn't fit bin")
	}
	b.items = append(b.items, i)
	b.size += i.Size
	b.size += i.Size()
	return nil
}

//fits checks if the items can be put into the bin.
func (b *Bin) fits(i Item) bool {
	return b.size+i.Size <= b.capacity
	return b.size+i.Size() <= b.capacity
}

//newBin allocates a new Bin ready to use.
func newBin(capacity int64) Bin {
	return Bin{capacity: capacity, items: []Item{}}
}

//Items returns the Items stored in the bin.
func (b *Bin) Items() []Item {
	return b.items
}

//BinsBySize implements sort.Interface, sorting Bins by the size of the contained items.
type BinsBySize []Bin

func (x BinsBySize) Len() int           { return len(x) }

M bin_test.go => bin_test.go +16 -9
@@ 7,35 7,42 @@ import (

func TestBinAdd(t *testing.T) {
	b := newBin(10)
	b.add(Item{Size: 3})
	b.add(Item{Size: 4})
	x := testItem(3)
	y := testItem(4)
	b.add(&x)
	b.add(&y)

	if len(b.items) != 2 {
		t.Fail()
	}

	if b.items[0].Size != 3 || b.items[1].Size != 4 {
	if b.items[0].Size() != 3 || b.items[1].Size() != 4 {
		t.Fail()
	}
}

func TestBinFits(t *testing.T) {
	b := newBin(10)
	b.add(Item{Size: 3})
	if b.fits(Item{Size: 8}) {
	x := testItem(3)
	y := testItem(8)
	z := testItem(6)
	b.add(&x)
	if b.fits(&y) {
		t.Fail()
	}

	if !b.fits(Item{Size: 6}) {
	if !b.fits(&z) {
		t.Fail()
	}
}

func TestBinItems(t *testing.T) {
	b := newBin(10)
	b.add(Item{Size: 3})
	b.add(Item{Size: 4})
	if b.Items()[0].Size != 3 || b.Items()[1].Size != 4 {
	x := testItem(3)
	y := testItem(4)
	b.add(&x)
	b.add(&y)
	if b.Items()[0].Size() != 3 || b.Items()[1].Size() != 4 {
		t.Fail()
	}
}

M cmd/fit/input.go => cmd/fit/input.go +10 -1
@@ 10,6 10,15 @@ import (
	"sr.ht/~rbn/fit"
)

type item struct {
	data []byte
	size int64
}

func (i *item) Size() int64 {
	return i.size
}

func parseDu(r io.Reader, blockSize int64) ([]fit.Item, error) {
	items := []fit.Item{}



@@ 25,7 34,7 @@ func parseDu(r io.Reader, blockSize int64) ([]fit.Item, error) {
			return nil, fmt.Errorf("invalid size in input: %v", s.Text())
		}

		items = append(items, fit.Item{Size: size * blockSize, Data: []byte(fs[1])})
		items = append(items, &item{size: size * blockSize, data: []byte(fs[1])})
	}

	if err := s.Err(); err != nil {

M cmd/fit/main.go => cmd/fit/main.go +3 -2
@@ 22,8 22,9 @@ const (
func printBins(bins []fit.Bin, w io.Writer) error {
	for _, bin := range bins {
		paths := []string{}
		for _, item := range bin.Items() {
			paths = append(paths, string(item.Data))
		for _, x := range bin.Items() {
			item := x.(*item) // we should only get type item here, so panicking is ok here
			paths = append(paths, string(item.data))
		}
		_, err := fmt.Fprintln(w, strings.Join(paths, "\t"))
		if err != nil {

M item.go => item.go +3 -4
@@ 1,12 1,11 @@
package fit

type Item struct {
	Data []byte
	Size int64
type Item interface {
	Size() int64
}

type ItemsBySize []Item

func (x ItemsBySize) Len() int           { return len(x) }
func (x ItemsBySize) Less(i, j int) bool { return x[i].Size < x[j].Size }
func (x ItemsBySize) Less(i, j int) bool { return x[i].Size() < x[j].Size() }
func (x ItemsBySize) Swap(i, j int)      { x[i], x[j] = x[j], x[i] }

M pack.go => pack.go +2 -2
@@ 16,8 16,8 @@ func Pack(binSelect BinSelectFunc, capacity int64, items []Item) ([]Bin, error) 
		var x Item
		x, items = items[0], items[1:]

		if x.Size > capacity {
			return nil, fmt.Errorf("item size exceeds bin capacity: size: %v capacity: %v", x.Size, capacity)
		if x.Size() > capacity {
			return nil, fmt.Errorf("item size exceeds bin capacity: size: %v capacity: %v", x.Size(), capacity)
		}
		var err error
		bins, err = binSelect(bins, capacity, x)

M select.go => select.go +7 -1
@@ 4,6 4,7 @@ import (
	"sort"
)

//First places the Item into the first Bin in which it fits.
func First(bins []Bin, capacity int64, x Item) ([]Bin, error) {
	for i, b := range bins {
		if !b.fits(x) {


@@ 21,6 22,7 @@ func First(bins []Bin, capacity int64, x Item) ([]Bin, error) {
	return bins, nil
}

//Best places the Item into the fullest Bin it fits.
func Best(bins []Bin, capacity int64, x Item) ([]Bin, error) {
	sort.Sort(sort.Reverse(BinsBySize(bins)))
	for i, b := range bins {


@@ 39,6 41,7 @@ func Best(bins []Bin, capacity int64, x Item) ([]Bin, error) {
	return bins, nil
}

//Worst places the Item into the least full Bin in which it fits.
func Worst(bins []Bin, capacity int64, x Item) ([]Bin, error) {
	sort.Sort(BinsBySize(bins))
	for i, b := range bins {


@@ 57,4 60,7 @@ func Worst(bins []Bin, capacity int64, x Item) ([]Bin, error) {
	return bins, nil
}

type BinSelectFunc func([]Bin, int64, Item) ([]Bin, error)
//BinSelectFunc is a strategy for placing an item into one of the bins.
//
//If required, a new bin is added with the given capacity.
type BinSelectFunc func(bins []Bin, capacity int64, item Item) ([]Bin, error)

M select_test.go => select_test.go +24 -19
@@ 1,39 1,44 @@
package fit

import (
	"bytes"
	"testing"
)

type selectTest struct {
	item Item
	item testItem
	bin  int
	pos  int
}

type testItem int64

func (t *testItem) Size() int64 {
	return int64(*t)
}

var firstTests = []selectTest{
	{
		item: Item{Data: []byte("a"), Size: 8},
		item: testItem(8),
		bin:  0,
		pos:  0,
	},
	{
		item: Item{Data: []byte("b"), Size: 1},
		item: testItem(1),
		bin:  0,
		pos:  1,
	},
	{
		item: Item{Data: []byte("c"), Size: 3},
		item: testItem(3),
		bin:  1,
		pos:  0,
	},
	{
		item: Item{Data: []byte("d"), Size: 1},
		item: testItem(1),
		bin:  0,
		pos:  2,
	},
	{
		item: Item{Data: []byte("e"), Size: 4},
		item: testItem(4),
		bin:  1,
		pos:  1,
	},


@@ 42,27 47,27 @@ var firstTests = []selectTest{
// one must swap the bins in head, as they are sorted in place. this isn't nice.
var bestTests = []selectTest{
	{
		item: Item{Data: []byte("a"), Size: 6},
		item: testItem(6),
		bin:  0,
		pos:  0,
	},
	{
		item: Item{Data: []byte("b"), Size: 8},
		item: testItem(8),
		bin:  1,
		pos:  0,
	},
	{
		item: Item{Data: []byte("c"), Size: 1},
		item: testItem(1),
		bin:  0,
		pos:  1,
	},
	{
		item: Item{Data: []byte("d"), Size: 2},
		item: testItem(2),
		bin:  1,
		pos:  1,
	},
	{
		item: Item{Data: []byte("e"), Size: 1},
		item: testItem(1),
		bin:  0,
		pos:  2,
	},


@@ 70,27 75,27 @@ var bestTests = []selectTest{

var worstTests = []selectTest{
	{
		item: Item{Data: []byte("a"), Size: 6},
		item: testItem(6),
		bin:  0,
		pos:  0,
	},
	{
		item: Item{Data: []byte("b"), Size: 8},
		item: testItem(8),
		bin:  1,
		pos:  0,
	},
	{
		item: Item{Data: []byte("c"), Size: 1},
		item: testItem(1),
		bin:  0,
		pos:  1,
	},
	{
		item: Item{Data: []byte("d"), Size: 2},
		item: testItem(2),
		bin:  0,
		pos:  2,
	},
	{
		item: Item{Data: []byte("e"), Size: 1},
		item: testItem(1),
		bin:  0,
		pos:  1,
	},


@@ 101,14 106,14 @@ func testSelect(t *testing.T, binSelect BinSelectFunc, tests []selectTest) {

	for _, test := range tests {
		var err error
		bins, err = binSelect(bins, 10, test.item)
		bins, err = binSelect(bins, 10, &test.item)
		if err != nil {
			t.FailNow()
		}
		t.Logf("%#v",bins)

		binItem := bins[test.bin].Items()[test.pos]
		if !bytes.Equal(binItem.Data, test.item.Data) || binItem.Size != test.item.Size {
		if binItem.Size() != test.item.Size() {
			t.Logf("items differ: %+v %+v", binItem, test.item)
			t.Fail()
		}