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 }