Skip to content

Commit

Permalink
Fix async execution and write multi-step searches
Browse files Browse the repository at this point in the history
Resolved an issue with the asynchronous execution: it is now actually
asynchronous and not a direct call to another function. There appears to
be a bug in the unit testing library that doesn't support usage of
`process.nextTick`, but for now this has been worked around by using the
less efficient `setTimeout callback, 0` function.

The search algorithm also now supports multiple step paths and caveats
identified around that have been documented in the code (regarding state
copying). Unit tests for the above have been written.
  • Loading branch information
Edd Porter committed Feb 7, 2012
1 parent e28ec98 commit 73d1a29
Show file tree
Hide file tree
Showing 4 changed files with 109 additions and 25 deletions.
1 change: 1 addition & 0 deletions .gitignore
@@ -1,2 +1,3 @@
*.js
node_modules
*.swp
73 changes: 53 additions & 20 deletions lib/search.coffee
@@ -1,3 +1,6 @@
util = require('util')
_ = require 'underscore'

Search = () ->
return

Expand All @@ -8,33 +11,63 @@ Search.prototype = {

next_actions: null

# (state, action)
# This function must not modify the `state` variable that is passed by
# reference. Instead, a fresh object _must_ be returned.
apply_action_to_state: null

search: (initial_state, callback) ->
frontier = [ [ { state: initial_state, action: null } ] ]
explored = []
loop
console.log 'New loop. Frontier length: ' + frontier.length
if frontier.length == 0
callback false if callback?
if callback != undefined
console.log 'found callback'
self = this
setTimeout () ->
console.log 'Going async'
self.search_loop frontier, explored, callback
, 0
else
this.search_loop frontier, explored, callback

search_loop: (frontier, explored, callback) ->
console.log 'New loop. Frontier length: ' + frontier.length
if frontier.length == 0
if callback?
callback false
return
else
return false
path = this.remove_choice frontier
console.log 'Next path choice: ' + require('util').inspect path
s = path[path.length - 1] # s = path.end
explored[explored.length] = s # add s to explored
console.log 'Number of explored states: ' + explored.length
if this.is_goal(s)
console.log 'Goal state found: ' + require('util').inspect s
callback true, path if callback?
[path, frontier] = this.remove_choice frontier
console.log 'Next path choice: ' + util.inspect path
s = path[path.length - 1] # s = path.end
explored[explored.length] = s # add s to explored
console.log 'Number of explored states: ' + explored.length
if this.is_goal(s.state)
console.log 'Goal state found: ' + util.inspect s
if callback?
callback true, path
return
else
return path
for a in this.next_actions(s)
result = this.apply_action_to_state s, a
if not result in frontier and not result in explored
new_path = path
new_path[new_path.length - 1] =
state: result
action: a
frontier[frontier.length - 1] = new_path
for a in this.next_actions(s.state)
console.log 'Trying actions: ' + util.inspect a
result = this.apply_action_to_state s.state, a
console.log 'Action state result: ' + util.inspect result
if not (result in frontier) and not (result in explored)
console.log 'New state'
new_path = path.slice(0)
new_path[new_path.length] =
state: result
action: a
frontier[frontier.length] = new_path
if callback != undefined
self = this
setTimeout () ->
console.log 'Looping'
self.search_loop frontier, explored, callback
, 0
else
this.search_loop frontier, explored, callback
}

exports.Search = Search
4 changes: 3 additions & 1 deletion package.json
Expand Up @@ -16,7 +16,9 @@
"engines": {
"node": ">= 0.6.10"
},
"dependencies": {},
"dependencies": {
"underscore": ">= 1.3.1"
},
"devDependencies": {
"nodeunit": ">= 0.6.4"
},
Expand Down
56 changes: 52 additions & 4 deletions test/search.coffee
Expand Up @@ -67,9 +67,16 @@ exports['search'] = nodeunit.testCase {
test.notEqual this.search.search, null
test.done()

'sync_allStatesAreGoal_returnsInitialState': (test) ->
'async_testLoopingPath_success': (test) ->
this.search.search_loop = (f, e, c) ->
console.log 'looping'
test.done()
this.search.search null, () ->
return

'sync_allStatesAreGoals_returnsInitialState': (test) ->
this.search.remove_choice = (frontier) ->
frontier[0]
[frontier[0], frontier[1..]]
this.search.is_goal = (state) ->
true
initial_state =
Expand All @@ -80,9 +87,9 @@ exports['search'] = nodeunit.testCase {
test.equal result[0].state, initial_state
test.done()

'async_allStatesAreGoal_returnsInitialState': (test) ->
'async_allStatesAreGoals_returnsInitialState': (test) ->
this.search.remove_choice = (frontier) ->
frontier[0]
[frontier[0], frontier[1..]]
this.search.is_goal = (state) ->
true
initial_state =
Expand All @@ -92,4 +99,45 @@ exports['search'] = nodeunit.testCase {
test.equal result.length, 1
test.equal result[0].state, initial_state
test.done()

'sync_allButStartStateAreGoals_returnsLengthTwoPath': (test) ->
initial_state =
data1: 'stuff'
data2: 81
this.search.remove_choice = (frontier) ->
[frontier[0], frontier[1..]]
this.search.is_goal = (state) ->
console.dir state
state != initial_state
this.search.next_actions = (state) ->
['up']
this.search.apply_action_to_state = (state, action) ->
{ data1: state.data1, data2: state.data2 + 1 }
result = this.search.search initial_state
test.equal result.length, 2
test.equal result[0].state, initial_state
test.notEqual result[1].state, initial_state
test.done()

'async_allButStartStateAreGoals_returnsLengthTwoPath': (test) ->
initial_state =
data1: 'stuff'
data2: 81
this.search.remove_choice = (frontier) ->
[frontier[0], frontier[1..]]
this.search.is_goal = (state) ->
console.dir state
state != initial_state
this.search.next_actions = (state) ->
['up']
this.search.apply_action_to_state = (state, action) ->
{ data1: state.data1, data2: state.data2 + 1 }
this.search.search initial_state, (success, result) ->
test.equal result.length, 2
test.equal result[0].state, initial_state
test.notEqual result[1].state, initial_state
test.done()

# TODO: Create a test for paths that check back on themselves thus
# testing the array 'contains' code and the rejection of the state
}

0 comments on commit 73d1a29

Please sign in to comment.