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 }