github.com/rormartin/gosearch@v0.0.0-20170227230419-1bddb8eed929/gsearch_test.go (about)

     1  package gosearch
     2  
     3  import (
     4  	"math"
     5  	"sort"
     6  	"strconv"
     7  	"testing"
     8  )
     9  
    10  type operation rune
    11  
    12  const (
    13  	sum operation = iota
    14  	sub
    15  	mul
    16  	div
    17  )
    18  
    19  type numberAction struct {
    20  	n1, n2 int
    21  	op     operation
    22  }
    23  
    24  func (action numberAction) Cost() float64 {
    25  	return 1.0
    26  }
    27  
    28  func (action numberAction) opResult() int {
    29  	switch action.op {
    30  	case sum:
    31  		return action.n1 + action.n2
    32  	case sub:
    33  		return action.n1 - action.n2
    34  	case mul:
    35  		return action.n1 * action.n2
    36  	case div:
    37  		if action.n2 != 0 {
    38  			if action.n1%action.n2 == 0 {
    39  				return action.n1 / action.n2
    40  			}
    41  		}
    42  	}
    43  	// error in other case
    44  	return -1
    45  }
    46  
    47  func (action numberAction) String() string {
    48  
    49  	var oper string
    50  	switch action.op {
    51  	case sum:
    52  		oper = "+"
    53  	case sub:
    54  		oper = "-"
    55  	case mul:
    56  		oper = "*"
    57  	case div:
    58  		oper = "/"
    59  	}
    60  
    61  	return "[" +
    62  		strconv.Itoa(action.n1) + oper + strconv.Itoa(action.n2) +
    63  		"=" + strconv.Itoa(action.opResult()) + "]"
    64  
    65  }
    66  
    67  type numbersState struct {
    68  	numbers []int
    69  	goal    int
    70  	actions []Action
    71  }
    72  
    73  func (state numbersState) String() string {
    74  
    75  	result := "["
    76  
    77  	for i, n := range state.numbers {
    78  		result += strconv.Itoa(n)
    79  		if i < len(state.numbers)-1 {
    80  			result += ","
    81  		}
    82  	}
    83  	result += " -> " + strconv.Itoa(state.goal) + "]"
    84  
    85  	result += "{"
    86  
    87  	for i, a := range state.actions {
    88  		result += a.(numberAction).String()
    89  		if i < len(state.actions)-1 {
    90  			result += ","
    91  		}
    92  	}
    93  	result += "}"
    94  
    95  	return result
    96  }
    97  
    98  func (state numbersState) ApplyAction(action Action) State {
    99  
   100  	nAction := action.(numberAction)
   101  
   102  	// clone numbers without the actions numbers
   103  	n1removed, n2removed := false, false
   104  	newNumbers := make([]int, len(state.numbers)-1)
   105  	index := 0
   106  	for _, n := range state.numbers {
   107  		if !n1removed && n == nAction.n1 {
   108  			n1removed = true
   109  		} else if !n2removed && n == nAction.n2 {
   110  			n2removed = true
   111  		} else {
   112  			newNumbers[index] = n
   113  			index++
   114  		}
   115  	}
   116  	// add the action result
   117  	newNumbers[len(newNumbers)-1] = nAction.opResult()
   118  
   119  	// clone actions and add the new result
   120  	newActions := make([]Action, len(state.actions)+1)
   121  	for i, a := range state.actions {
   122  		newActions[i] = a
   123  	}
   124  
   125  	newActions[len(newActions)-1] = nAction
   126  
   127  	return numbersState{
   128  		numbers: newNumbers, goal: state.goal, actions: newActions}
   129  
   130  }
   131  
   132  func (state numbersState) GetPartialSolution() []Action {
   133  	return state.actions
   134  }
   135  
   136  func (state numbersState) GetSolutionCost() float64 {
   137  	var result float64
   138  	for _, act := range state.actions {
   139  		result += act.Cost()
   140  	}
   141  	return result
   142  }
   143  
   144  func (state numbersState) isValidAction(action Action) bool {
   145  	return (action.(numberAction)).opResult() > 0
   146  }
   147  
   148  func (state numbersState) GetApplicableActions() []Action {
   149  	actions := []Action{}
   150  
   151  	for i1, n1 := range state.numbers {
   152  		for i2 := i1 + 1; i2 < len(state.numbers); i2++ {
   153  			n2 := state.numbers[i2]
   154  
   155  			allActions := []numberAction{
   156  				{n1: n1, n2: n2, op: sum},
   157  				{n1: n1, n2: n2, op: sub},
   158  				{n1: n2, n2: n1, op: sub},
   159  				{n1: n1, n2: n2, op: mul},
   160  				{n1: n1, n2: n2, op: div},
   161  				{n1: n2, n2: n1, op: div},
   162  			}
   163  
   164  			for _, act := range allActions {
   165  				if state.isValidAction(act) {
   166  					actions = append(actions, act)
   167  				}
   168  			}
   169  		}
   170  	}
   171  
   172  	return actions
   173  }
   174  
   175  func (state numbersState) IsSolution() bool {
   176  	for _, num := range state.numbers {
   177  		if num == state.goal {
   178  			return true
   179  		}
   180  	}
   181  	return false
   182  }
   183  
   184  func (state numbersState) Equal(second State) bool {
   185  	state2 := second.(numbersState)
   186  	if state.goal != state2.goal {
   187  		return false
   188  	}
   189  
   190  	if len(state.numbers) != len(state2.numbers) {
   191  		return false
   192  	}
   193  
   194  	numbers1 := state.numbers
   195  	sort.Ints(numbers1)
   196  	numbers2 := state2.numbers
   197  	sort.Ints(numbers2)
   198  
   199  	for i := range numbers1 {
   200  		if numbers1[i] != numbers2[i] {
   201  			return false
   202  		}
   203  	}
   204  
   205  	return true
   206  }
   207  
   208  func (state numbersState) addActionToSolution(action Action) {
   209  	state.actions = append(state.actions, action.(numberAction))
   210  }
   211  
   212  func (state numbersState) GetStateLevel() int {
   213  	return len(state.actions)
   214  }
   215  
   216  // to string for the custom action
   217  func action2string(actions []Action) string {
   218  	result := "{"
   219  	for i, act := range actions {
   220  		result += act.(numberAction).String()
   221  		if i < len(actions)-1 {
   222  			result += ","
   223  		}
   224  	}
   225  	result += "}"
   226  	return result
   227  }
   228  
   229  func (state numbersState) Heuristic() float64 {
   230  
   231  	mindiff := float64(state.goal)
   232  	var tmpdiff float64
   233  
   234  	for _, num := range state.numbers {
   235  		tmpdiff = math.Abs(float64(state.goal - num))
   236  		if tmpdiff < mindiff {
   237  			mindiff = tmpdiff
   238  		}
   239  	}
   240  
   241  	return mindiff
   242  
   243  }
   244  
   245  // -- TEST START --
   246  
   247  func TestOneStepD(t *testing.T) {
   248  
   249  	initState := numbersState{
   250  		numbers: []int{2, 4}, goal: 6, actions: []Action{}}
   251  
   252  	solution, stats := SearchDepthFirst(initState)
   253  
   254  	t.Logf("%s -> %s", initState.String(), action2string(solution))
   255  	t.Logf("%s", stats.String())
   256  
   257  	if len(solution) != 1 {
   258  		t.Errorf("Wrong solution for %s", initState.String())
   259  	}
   260  }
   261  
   262  func TestOneStepB(t *testing.T) {
   263  
   264  	initState := numbersState{
   265  		numbers: []int{2, 4}, goal: 6, actions: []Action{}}
   266  
   267  	solution, stats := SearchBreadthFirst(initState)
   268  
   269  	t.Logf("%s -> %s", initState.String(), action2string(solution))
   270  	t.Logf("%s", stats.String())
   271  
   272  	if len(solution) != 1 {
   273  		t.Errorf("Wrong solution for %s", initState.String())
   274  	}
   275  }
   276  
   277  func TestNoSolutionD(t *testing.T) {
   278  
   279  	initState := numbersState{
   280  		numbers: []int{2, 4}, goal: 3, actions: []Action{}}
   281  
   282  	solution, stats := SearchDepthFirst(initState)
   283  
   284  	t.Logf("%s -> %s", initState.String(), action2string(solution))
   285  	t.Logf("%s", stats.String())
   286  
   287  	if len(solution) != 0 {
   288  		t.Errorf("Wrong solution for %s", initState.String())
   289  	}
   290  }
   291  
   292  func TestNoSolutionB(t *testing.T) {
   293  
   294  	initState := numbersState{
   295  		numbers: []int{2, 4}, goal: 3, actions: []Action{}}
   296  
   297  	solution, stats := SearchBreadthFirst(initState)
   298  
   299  	t.Logf("%s -> %s", initState.String(), action2string(solution))
   300  	t.Logf("%s", stats.String())
   301  
   302  	if len(solution) != 0 {
   303  		t.Errorf("Wrong solution for %s", initState.String())
   304  	}
   305  }
   306  
   307  func TestNoSolutionID(t *testing.T) {
   308  
   309  	initState := numbersState{
   310  		numbers: []int{2, 4}, goal: 3, actions: []Action{}}
   311  
   312  	solution, stats := SearchIterativeDepth(initState)
   313  
   314  	t.Logf("%s -> %s", initState.String(), action2string(solution))
   315  	t.Logf("%s", stats.String())
   316  
   317  	if len(solution) != 0 {
   318  		t.Errorf("Wrong solution for %s", initState.String())
   319  	}
   320  }
   321  
   322  func TestNoSolutionAstar(t *testing.T) {
   323  
   324  	initState := numbersState{
   325  		numbers: []int{2, 4}, goal: 3, actions: []Action{}}
   326  
   327  	solution, stats := SearchAstar(initState)
   328  
   329  	t.Logf("%s -> %s", initState.String(), action2string(solution))
   330  	t.Logf("%s", stats.String())
   331  
   332  	if len(solution) != 0 {
   333  		t.Errorf("Wrong solution for %s", initState.String())
   334  	}
   335  }
   336  
   337  func TestStandardProblem1D(t *testing.T) {
   338  
   339  	initState := numbersState{
   340  		numbers: []int{2, 4, 5, 10, 25, 7}, goal: 1811, actions: []Action{}}
   341  
   342  	solution, stats := SearchDepthFirst(initState)
   343  
   344  	t.Logf("%s -> %s", initState.String(), action2string(solution))
   345  	t.Logf("%s", stats.String())
   346  
   347  	if len(solution) == 0 {
   348  		t.Errorf("No solution found for %s", initState.String())
   349  	}
   350  
   351  }
   352  
   353  func TestStandardProblem1B(t *testing.T) {
   354  
   355  	initState := numbersState{
   356  		numbers: []int{2, 4, 5, 10, 25, 7}, goal: 1811, actions: []Action{}}
   357  
   358  	solution, stats := SearchBreadthFirst(initState)
   359  
   360  	t.Logf("%s -> %s", initState.String(), action2string(solution))
   361  	t.Logf("%s", stats.String())
   362  
   363  	if len(solution) == 0 {
   364  		t.Errorf("No solution found for %s", initState.String())
   365  	}
   366  
   367  }
   368  
   369  func TestStandardProblem1ID(t *testing.T) {
   370  
   371  	initState := numbersState{
   372  		numbers: []int{2, 4, 5, 10, 25, 7}, goal: 1811, actions: []Action{}}
   373  
   374  	solution, stats := SearchIterativeDepth(initState)
   375  
   376  	t.Logf("%s -> %s", initState.String(), action2string(solution))
   377  	t.Logf("%s", stats.String())
   378  
   379  	if len(solution) == 0 {
   380  		t.Errorf("No solution found for %s", initState.String())
   381  	}
   382  
   383  }
   384  
   385  func TestStandardProblemAstar(t *testing.T) {
   386  
   387  	initState := numbersState{
   388  		numbers: []int{2, 4, 5, 10, 25, 7}, goal: 1811, actions: []Action{}}
   389  
   390  	solution, stats := SearchAstar(initState)
   391  
   392  	t.Logf("%s -> %s", initState.String(), action2string(solution))
   393  	t.Logf("%s", stats.String())
   394  
   395  	if len(solution) == 0 {
   396  		t.Errorf("No solution found for %s", initState.String())
   397  	}
   398  
   399  }
   400  
   401  func BenchmarkNumbersDepthFirst(b *testing.B) {
   402  
   403  	initState := numbersState{
   404  		numbers: []int{2, 4, 5, 10, 25, 7}, goal: 1811, actions: []Action{}}
   405  
   406  	solution, stats := SearchDepthFirst(initState)
   407  
   408  	b.Logf("%s -> %s", initState.String(), action2string(solution))
   409  	b.Logf("%s", stats.String())
   410  }
   411  
   412  func BenchmarkNumbersBreadthFirst(b *testing.B) {
   413  
   414  	initState := numbersState{
   415  		numbers: []int{2, 4, 5, 10, 25, 7}, goal: 1811, actions: []Action{}}
   416  
   417  	solution, stats := SearchBreadthFirst(initState)
   418  
   419  	b.Logf("%s -> %s", initState.String(), action2string(solution))
   420  	b.Logf("%s", stats.String())
   421  }
   422  
   423  func BenchmarkNumbersIterativeDepth(b *testing.B) {
   424  
   425  	initState := numbersState{
   426  		numbers: []int{2, 4, 5, 10, 25, 7}, goal: 1811, actions: []Action{}}
   427  
   428  	solution, stats := SearchIterativeDepth(initState)
   429  
   430  	b.Logf("%s -> %s", initState.String(), action2string(solution))
   431  	b.Logf("%s", stats.String())
   432  }
   433  
   434  func BenchmarkNumbersAstar(b *testing.B) {
   435  
   436  	initState := numbersState{
   437  		numbers: []int{2, 4, 5, 10, 25, 7}, goal: 1811, actions: []Action{}}
   438  
   439  	solution, stats := SearchAstar(initState)
   440  
   441  	b.Logf("%s -> %s", initState.String(), action2string(solution))
   442  	b.Logf("%s", stats.String())
   443  }