diff --git a/cmd/buckets-of-fun/main.go b/cmd/buckets-of-fun/main.go deleted file mode 100644 index 4bf6dc2..0000000 --- a/cmd/buckets-of-fun/main.go +++ /dev/null @@ -1,25 +0,0 @@ -package main - -import ( - "flag" - "fmt" - "os" - - "git.buddy.wtf/challenges/buckets-of-fun/lib/application" -) - -func main() { - if err := run(); err != nil { - fmt.Fprintln(os.Stderr, err) - os.Exit(1) - } -} - -func run() error { - flag.Parse() - app, err := application.New(flag.Args()...) - if err != nil { - return err - } - return app.Run() -} diff --git a/go.mod b/go.mod deleted file mode 100644 index f7e05fc..0000000 --- a/go.mod +++ /dev/null @@ -1,3 +0,0 @@ -module git.buddy.wtf/challenges/buckets-of-fun - -go 1.13 diff --git a/lib/application/app.go b/lib/application/app.go deleted file mode 100644 index 849f469..0000000 --- a/lib/application/app.go +++ /dev/null @@ -1,52 +0,0 @@ -package application - -import ( - "errors" - "fmt" - "strconv" - - "git.buddy.wtf/challenges/buckets-of-fun/lib/bucket" -) - -// App holds context for the application -type App struct { - Target uint64 - Buckets bucket.Buckets -} - -// New returns an instance of the app -func New(args ...string) (*App, error) { - if len(args) < 2 { - msg := fmt.Sprintf("usage error: requires at least 2 arguments, received %d", len(args)) - return nil, errors.New(msg) - } - target, err := strconv.ParseUint(args[0], 10, 64) - if err != nil { - return nil, errors.New("target must be a positive integer: " + err.Error()) - } - - app := App{ - Target: target, - Buckets: bucket.Buckets{}, - } - - for _, arg := range args[1:] { - capacity, err := strconv.ParseUint(arg, 10, 64) - if err != nil { - return nil, errors.New("bucket capacity must be a positive integer: " + err.Error()) - } - app.Buckets = append(app.Buckets, bucket.Bucket{Capacity: capacity}) - } - - return &app, nil -} - -// Run runs the application -func (a *App) Run() error { - fmt.Printf("target: %d\n", a.Target) - fmt.Printf("buckets: %s\n", a.Buckets.String()) - for _, solution := range a.Buckets.FindTarget((a.Target)) { - fmt.Println(solution) - } - return nil -} diff --git a/lib/bucket/bucket.go b/lib/bucket/bucket.go deleted file mode 100644 index 7e6f563..0000000 --- a/lib/bucket/bucket.go +++ /dev/null @@ -1,85 +0,0 @@ -package bucket - -import "fmt" - -// Bucket to store volume -type Bucket struct { - // Capacity amount the bucket can store - Capacity uint64 - // Volume is the current volume of the bucket - Volume uint64 -} - -// Fill returns a copy Bucket filled -func Fill(b Bucket) Bucket { - return Bucket{ - Volume: b.Capacity, - Capacity: b.Capacity, - } -} - -// Empty returns a copy Bucket set to empty -func Empty(b Bucket) Bucket { - return Bucket{ - Volume: 0, - Capacity: b.Capacity, - } -} - -// Pour fills the target bucket to the top -func Pour(src, target Bucket) (Bucket, Bucket) { - s := Bucket{ - Capacity: src.Capacity, - Volume: src.Volume, - } - t := Bucket{ - Capacity: target.Capacity, - Volume: target.Volume, - } - availableVolume := t.Capacity - t.Volume - if availableVolume > s.Volume { - t.Volume += s.Volume - s.Volume = 0 - } else { - s.Volume -= availableVolume - t.Volume += availableVolume - } - return s, t -} - -// Fill sets the volume to the capacity to fill bucket -func (b *Bucket) Fill() { - b.Volume = b.Capacity -} - -// Empty sets the volume to 0 -func (b *Bucket) Empty() { - b.Volume = 0 -} - -// IsEmpty is true no volume -func (b *Bucket) IsEmpty() bool { - return b.Volume == 0 -} - -// IsFull is true if bucket is at capacity -func (b *Bucket) IsFull() bool { - return b.Volume == b.Capacity -} - -// Pour fills the target bucket to the top -func (b *Bucket) Pour(target *Bucket) { - availableVolume := target.Capacity - target.Volume - if availableVolume > b.Volume { - target.Volume += b.Volume - b.Volume = 0 - } else { - b.Volume -= availableVolume - target.Volume += availableVolume - } -} - -// String gets string representation -func (b *Bucket) String() string { - return fmt.Sprintf("%dvol/%dcap", b.Volume, b.Capacity) -} diff --git a/lib/bucket/bucket_test.go b/lib/bucket/bucket_test.go deleted file mode 100644 index 476ab63..0000000 --- a/lib/bucket/bucket_test.go +++ /dev/null @@ -1,28 +0,0 @@ -package bucket - -import "testing" - -func TestPour(t *testing.T) { - var testcases = []struct { - source *Bucket - target *Bucket - expectedSrcVol uint64 - expectedTargetVol uint64 - }{ - {&Bucket{Capacity: 5, Volume: 2}, &Bucket{Capacity: 3, Volume: 2}, 1, 3}, - {&Bucket{Capacity: 5, Volume: 1}, &Bucket{Capacity: 3, Volume: 1}, 0, 2}, - {&Bucket{Capacity: 5, Volume: 0}, &Bucket{Capacity: 3, Volume: 0}, 0, 0}, - {&Bucket{Capacity: 5, Volume: 5}, &Bucket{Capacity: 3, Volume: 3}, 5, 3}, - {&Bucket{Capacity: 5, Volume: 4}, &Bucket{Capacity: 3, Volume: 2}, 3, 3}, - } - - for _, tc := range testcases { - tc.source.Pour(tc.target) - if tc.source.Volume != tc.expectedSrcVol { - t.Errorf("expected source volume %d, got = %d", tc.expectedSrcVol, tc.source.Volume) - } - if tc.target.Volume != tc.expectedTargetVol { - t.Errorf("expected target volume %d, got = %d", tc.expectedTargetVol, tc.target.Volume) - } - } -} diff --git a/lib/bucket/buckets.go b/lib/bucket/buckets.go deleted file mode 100644 index b16e5c6..0000000 --- a/lib/bucket/buckets.go +++ /dev/null @@ -1,267 +0,0 @@ -package bucket - -import ( - "fmt" - "sort" - "strings" -) - -// Buckets manages bucket objects -type Buckets []Bucket - -// // action name -type actionType int - -const ( - actionTypeUnknown actionType = iota - actionTypeEmpty - actionTypeFill - actionTypePour -) - -type action struct { - kind actionType - src int - target int -} - -type actions []action - -func (a actions) String() string { - acts := []string{} - for _, act := range a { - acts = append(acts, act.String()) - } - return fmt.Sprintf("[%s]", strings.Join(acts, ",")) -} - -func (a action) String() string { - switch a.kind { - case actionTypeEmpty: - return fmt.Sprintf("empty %d ", a.target) - case actionTypeFill: - return fmt.Sprintf("fill %d ", a.target) - case actionTypePour: - return fmt.Sprintf("pour %d → %d", a.src, a.target) - case actionTypeUnknown: - } - return fmt.Sprintf("unknown (src: %d, target: %d)", a.src, a.target) -} - -type result struct { - mapping map[string][]actions - matched map[string]interface{} -} - -func (r *result) get(key string) ([]actions, bool) { - acts, ok := r.mapping[key] - return acts, ok -} - -func (r *result) keys() []string { - keys := []string{} - for key := range r.mapping { - keys = append(keys, key) - } - sort.Strings(keys) - return keys -} - -func (r *result) matchedKeys() []string { - keys := []string{} - for key := range r.matched { - keys = append(keys, key) - } - sort.Strings(keys) - return keys -} - -func (r *result) bestMatches() ([]string, bool) { - var best []string - found := false - numActions := 0 - - matched := r.matchedKeys() - for _, key := range matched { - acts, ok := r.get(key) - if found == false && ok && len(acts) > 0 { - found = true - numActions = len(acts[0]) - best = []string{key} - continue - } - - if numActions > len(acts[0]) { - numActions = len(acts[0]) - best = []string{key} - } else if numActions == len(acts[0]) { - best = append(best, key) - } - } - - if !found { - return nil, false - } - return best, true -} - -func (r *result) addMatch(key string) { - r.matched[key] = struct{}{} -} - -func (r *result) addActions(key string, acts actions) bool { - currentActions, ok := r.mapping[key] - if !ok || len(currentActions) == 0 { - currentActions = []actions{acts} - r.mapping[key] = currentActions - return true - } - - if len(acts) > len(currentActions[0]) { - return false - } - - if len(acts) == len(currentActions[0]) { - r.mapping[key] = append(currentActions, acts) - return true - } - - r.mapping[key] = []actions{acts} - return true -} - -func (b Buckets) empty(index int) { - buk := &b[index] - buk.Empty() - b[index] = *buk -} - -func (b Buckets) fill(index int) { - buk := &b[index] - buk.Fill() - b[index] = *buk -} - -func (b Buckets) pour(i, j int) { - src := &b[i] - dest := &b[j] - src.Pour(dest) - b[i] = *src - b[j] = *dest -} - -// HasTarget returns true -func (b Buckets) HasTarget(target uint64) bool { - for _, bucket := range b { - if bucket.Volume == target { - return true - } - } - return false -} - -func (b Buckets) String() string { - bucketStrs := []string{} - for _, bucket := range b { - bucketStrs = append(bucketStrs, bucket.String()) - } - return fmt.Sprintf("[%s]", strings.Join(bucketStrs, ",")) -} - -// FindTarget returns actions to find target -func (b Buckets) FindTarget(target uint64) []string { - r := &result{ - mapping: map[string][]actions{}, - matched: map[string]interface{}{}, - } - b.findTarget(target, r, actions{}) - - keys, found := r.bestMatches() - if !found { - return []string{"No solution"} - } - fmt.Printf("%d solutions\n", len(keys)) - for i, key := range keys { - allacts, ok := r.get(key) - if !ok { - continue - } - newState := make(Buckets, len(b)) - copy(newState, b) - fmt.Printf("solution %d\n", i+1) - for _, acts := range allacts { - if len(acts) == 0 { - fmt.Println("no actions required") - continue - } - for _, act := range acts { - newState = newState.performAction(act) - fmt.Printf("%s\t%s\n", act.String(), newState.String()) - } - } - } - ret := []string{} - return ret -} - -func (b Buckets) performAction(act action) Buckets { - newState := make(Buckets, len(b)) - copy(newState, b) - - switch act.kind { - case actionTypeEmpty: - newState.empty(act.target) - case actionTypeFill: - newState.fill(act.target) - case actionTypePour: - newState.pour(act.src, act.target) - } - - return newState -} - -func (b Buckets) findTarget( - target uint64, - states *result, - prevActions actions, -) { - key := b.String() - - added := states.addActions(key, prevActions) - - if b.HasTarget(target) { - states.addMatch(key) - return - } - - if !added { - return - } - - possibleActions := b.possibleActions() - for _, act := range possibleActions { - newActions := append(prevActions, act) - newState := b.performAction(act) - newState.findTarget(target, states, newActions) - } -} - -func (b Buckets) possibleActions() actions { - ret := actions{} - for i, bucket := range b { - if !bucket.IsFull() { - ret = append(ret, action{kind: actionTypeFill, target: i}) - } - if !bucket.IsEmpty() { - // ret = append(ret, action{kind: actionTypeEmpty, fn: b.empty(i), target: i}) - ret = append(ret, action{kind: actionTypeEmpty, target: i}) - for j, target := range b { - if !target.IsFull() && i != j { - // ret = append(ret, action{kind: actionTypePour, fn: b.pour(i, j), src: i, target: j}) - ret = append(ret, action{kind: actionTypePour, src: i, target: j}) - } - } - } - } - return ret -}