Skip to content

Commit 9f465e3

Browse files
Merge pull request kennyledet#227 from Yonaba/master
Iterative Deepening Depth First Search
2 parents 17fff76 + ce7dcb7 commit 9f465e3

11 files changed

Lines changed: 437 additions & 5 deletions

File tree

Breadth_First_Search/Lua/Yonaba/bfs_test.lua

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ run('Testing linear graph', function()
2828
local comp = function(a, b) return a.value == b end
2929
local ln_handler = require 'linear_handler'
3030
ln_handler.init(-2,5)
31-
local bfs = BFS(ln_handler)
31+
local bfs = BFS(ln_handler)
3232
local start, goal = ln_handler.getNode(0), ln_handler.getNode(5)
3333
assert(same(bfs:findPath(start, goal), {0,1,2,3,4,5}, comp))
3434

@@ -41,7 +41,7 @@ run('Testing grid graph', function()
4141
local gm_handler = require 'gridmap_handler'
4242
local bfs = BFS(gm_handler)
4343
local map = {{0,0,0,0,0},{0,1,1,1,1},{0,0,0,0,0}}
44-
gm_handler.init(map)
44+
gm_handler.init(map)
4545

4646
gm_handler.diagonal = false
4747
local start, goal = gm_handler.getNode(1,1), gm_handler.getNode(5,3)

Depth_First_Search/Lua/Yonaba/dfs.lua

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ local function backtrace(node)
5252
return path
5353
end
5454

55-
-- Initializes Breadth-Fist search with a custom handler
55+
-- Initializes Depth-Fist search with a custom handler
5656
local DFS = class()
5757
function DFS:initialize(handler)
5858
self.handler = handler

Depth_Limited_Search/Lua/Yonaba/dls.lua

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,14 @@ local function backtrace(node)
4343
return path
4444
end
4545

46-
-- Initializes Breadth-First search with a custom handler
46+
-- Initializes Depth-Limited search with a custom handler
4747
local DLS = class()
4848
function DLS:initialize(handler)
4949
self.handler = handler
5050
end
5151

5252
-- Clears all nodes for a next search
53-
-- Must be called inbetween consecutive searchs
53+
-- Must be called in-between consecutive searches
5454
function DLS:resetForNextSearch()
5555
local nodes = self.handler.getAllNodes()
5656
for _, node in ipairs(nodes) do
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
-- Minimalistic class implementation
2+
3+
local class = function(attr)
4+
local klass = attr or {}
5+
klass.__index = klass
6+
klass.__call = function(_,...) return klass:new(...) end
7+
function klass:new(...)
8+
local instance = setmetatable({}, klass)
9+
if klass.initialize then klass.initialize(instance, ...) end
10+
return instance
11+
end
12+
return setmetatable(klass,{__call = klass.__call})
13+
end
14+
15+
return class
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
-- A grid map handler
2+
-- Supports 4-directions and 8-directions moves
3+
4+
-- This handler is devised for 2d bounded grid where nodes are indexed
5+
-- with a pair of (x, y) coordinates. It features straight (4-directions)
6+
-- and diagonal (8-directions) moves.
7+
8+
-- Implements Node class (from node.lua)
9+
local Node = require 'node'
10+
function Node:initialize(x, y) self.x, self.y = x, y end
11+
function Node:toString() return ('Node: x = %d, y = %d'):format(self.x, self.y) end
12+
function Node:isEqualTo(n) return self.x == n.x and self.y == n.y end
13+
14+
-- Direction vectors for straight moves
15+
local orthogonal = {
16+
{x = 0, y = -1},
17+
{x = -1, y = 0},
18+
{x = 1, y = 0},
19+
{x = 0, y = 1},
20+
}
21+
22+
-- Direction vectors for diagonal moves
23+
local diagonal = {
24+
{x = -1, y = -1},
25+
{x = 1, y = -1},
26+
{x = -1, y = 1},
27+
{x = 1, y = 1}
28+
}
29+
30+
-- Checks of a given location is walkable on the grid.
31+
-- Assumes 0 is walkable, any other value is unwalkable.
32+
local function isWalkable(map, x, y) return map[y] and map[y][x] and map[y][x] == 0 end
33+
34+
local nodes = {}
35+
36+
-- Handler implementation
37+
local handler = {}
38+
39+
-- Inits the search space
40+
function handler.init(map)
41+
handler.map = map
42+
nodes = {}
43+
for y, line in ipairs(map) do
44+
for x in ipairs(line) do
45+
table.insert(nodes, Node(x, y))
46+
end
47+
end
48+
end
49+
50+
-- Returns an array of all nodes in the graph
51+
function handler.getAllNodes() return nodes end
52+
53+
-- Returns a Node
54+
function handler.getNode(x, y)
55+
local h, w = #handler.map, #handler.map[1]
56+
local k = (y - 1) * w + (x%w == 0 and w or x)
57+
return nodes[k]
58+
end
59+
60+
-- Returns manhattan distance between node a and node b
61+
function handler.distance(a, b)
62+
local dx, dy = a.x - b.x, a.y - b.y
63+
return math.abs(dx) + math.abs(dy)
64+
end
65+
66+
-- Returns an array of neighbors of node n
67+
function handler.getNeighbors(n)
68+
local neighbors = {}
69+
for _, axis in ipairs(orthogonal) do
70+
local x, y = n.x + axis.x, n.y + axis.y
71+
if isWalkable(handler.map, x, y) then
72+
table.insert(neighbors, handler.getNode(x, y))
73+
end
74+
end
75+
if handler.diagonal then
76+
for _, axis in ipairs(diagonal) do
77+
local x, y = n.x + axis.x, n.y + axis.y
78+
if isWalkable(handler.map, x, y) then
79+
table.insert(neighbors, handler.getNode(x,y))
80+
end
81+
end
82+
end
83+
return neighbors
84+
end
85+
86+
return handler
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
-- Generic Iterative Deepening Depth-First search algorithm implementation
2+
-- See : http://en.wikipedia.org/wiki/Iterative_deepening_depth-first_search
3+
4+
-- Notes : this is a generic implementation of IDDFS algorithm.
5+
-- It is devised to be used on any type of graph (point-graph, tile-graph,
6+
-- or whatever. It expects to be initialized with a handler, which acts as
7+
-- an interface between the search algorithm and the search space.
8+
9+
-- The IDDFS class expects a handler to be initialized. Roughly said, the handler
10+
-- is an interface between your search space and the generic search algorithm.
11+
-- This ensures flexibility, so that the generic algorithm can be adapted to
12+
-- search on any kind of space.
13+
-- The passed-in handler should implement those functions.
14+
-- handler.getNode(...) -> returns a Node (instance of node.lua)
15+
-- handler.getNeighbors(n) -> returns an array of all nodes that can be reached
16+
-- via node n (also called successors of node n)
17+
18+
-- The actual implementation uses recursion to look up for the path.
19+
-- Between consecutive path requests, the user must call the :resetForNextSearch()
20+
-- method to clear all nodes data created during a previous search.
21+
22+
-- The generic Node class provided (see node.lua) should also be implemented
23+
-- through the handler. Basically, it should describe how nodes are labelled
24+
-- and tested for equality for a custom search space.
25+
-- The following functions should be implemented:
26+
-- function Node:initialize(...) -> creates a Node with custom attributes
27+
-- function Node:isEqualTo(n) -> returns if self is equal to node n
28+
-- function Node:toString() -> returns a unique string representation of
29+
-- the node, for debug purposes
30+
31+
-- See custom handlers for reference (*_hander.lua).
32+
33+
-- Dependencies
34+
local class = require 'class'
35+
36+
-- Builds and returns the path to the goal node
37+
local function backtrace(node)
38+
local path = {}
39+
repeat
40+
table.insert(path, 1, node)
41+
node = node.parent
42+
until not node
43+
return path
44+
end
45+
46+
-- Runs a depth limited search
47+
local function depthLimitedSearch(finder, start, goal, depth)
48+
if start == goal then return backtrace(start) end
49+
if depth == 0 then return end
50+
start.visited = true
51+
local neighbors = finder.handler.getNeighbors(start)
52+
for _, neighbor in ipairs(neighbors) do
53+
if not neighbor.visited then
54+
neighbor.parent = start
55+
local foundGoal = depthLimitedSearch(finder, neighbor, goal, depth - 1)
56+
if foundGoal then return foundGoal end
57+
end
58+
end
59+
end
60+
61+
-- Initializes IDDFS with a custom handler, and an maximum
62+
-- depth search, which arbitrarily defaults to 10.
63+
local IDDFS = class()
64+
function IDDFS:initialize(handler, maxDepth)
65+
self.handler = handler
66+
self.maxDepth = maxDepth or 10 -- arbitrary constant
67+
end
68+
69+
-- Clears all nodes for a next search
70+
-- Must be called in-between consecutive searches
71+
function IDDFS:resetForNextSearch()
72+
local nodes = self.handler.getAllNodes()
73+
for _, node in ipairs(nodes) do
74+
node.visited, node.parent = nil, nil
75+
end
76+
end
77+
78+
-- Returns the path between start and goal locations
79+
-- start : a Node representing the start location
80+
-- goal : a Node representing the target location
81+
-- returns : an array of nodes
82+
function IDDFS:findPath(start, goal)
83+
for depth = 1, self.maxDepth do
84+
self:resetForNextSearch()
85+
local p = depthLimitedSearch(self, start, goal, depth)
86+
if p then return p end
87+
end
88+
end
89+
90+
return IDDFS
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
-- Tests for iddfs.lua
2+
local IDDFS = require 'iddfs'
3+
4+
local total, pass = 0, 0
5+
6+
local function dec(str, len)
7+
return #str < len
8+
and str .. (('.'):rep(len-#str))
9+
or str:sub(1,len)
10+
end
11+
12+
local function same(t, p, comp)
13+
for k,v in ipairs(t) do
14+
if not comp(v, p[k]) then return false end
15+
end
16+
return true
17+
end
18+
19+
local function run(message, f)
20+
total = total + 1
21+
local ok, err = pcall(f)
22+
if ok then pass = pass + 1 end
23+
local status = ok and 'PASSED' or 'FAILED'
24+
print(('%02d. %68s: %s'):format(total, dec(message,68), status))
25+
end
26+
27+
run('Testing linear graph', function()
28+
local comp = function(a, b) return a.value == b end
29+
local ln_handler = require 'linear_handler'
30+
ln_handler.init(-2,5)
31+
local iddfs = IDDFS(ln_handler)
32+
33+
local start, goal = ln_handler.getNode(0), ln_handler.getNode(5)
34+
assert(same(iddfs:findPath(start, goal), {0,1,2,3,4,5}, comp))
35+
36+
iddfs.maxDepth = 4
37+
assert(not iddfs:findPath(start, goal))
38+
39+
iddfs.maxDepth = 8
40+
start, goal = ln_handler.getNode(-2), ln_handler.getNode(5)
41+
assert(same(iddfs:findPath(start, goal), {-2,-1,0,1,2,3,4,5}, comp))
42+
43+
iddfs.maxDepth = 6
44+
assert(not iddfs:findPath(start, goal))
45+
end)
46+
47+
run('Testing grid graph', function()
48+
local comp = function(a, b) return a.x == b[1] and a.y == b[2] end
49+
local gm_handler = require 'gridmap_handler'
50+
local iddfs = IDDFS(gm_handler)
51+
local map = {{0,0,0,0,0},{0,1,1,1,1},{0,0,0,0,0}}
52+
gm_handler.init(map)
53+
54+
local start, goal = gm_handler.getNode(1,1), gm_handler.getNode(5,3)
55+
iddfs.maxDepth = 15
56+
assert(same(iddfs:findPath(start, goal), {{1,1},{1,2},{1,3},{2,3},{3,3},{4,3},{5,3}}, comp))
57+
58+
iddfs.maxDepth = 5
59+
assert(not iddfs:findPath(start, goal))
60+
end)
61+
62+
run('Testing point graph', function()
63+
local comp = function(a, b) return a.x == b[1] and a.y == b[2] end
64+
local pg_handler = require 'point_graph_handler'
65+
local iddfs = IDDFS(pg_handler)
66+
67+
pg_handler.addNode('a')
68+
pg_handler.addNode('b')
69+
pg_handler.addNode('c')
70+
pg_handler.addNode('d')
71+
pg_handler.addEdge('a', 'b')
72+
pg_handler.addEdge('a', 'c')
73+
pg_handler.addEdge('b', 'd')
74+
75+
local comp = function(a, b) return a.name == b end
76+
local start, goal = pg_handler.getNode('a'), pg_handler.getNode('d')
77+
iddfs.maxDepth = 3
78+
assert(same(iddfs:findPath(start, goal, 3), {'a','b','d'}, comp))
79+
80+
iddfs.maxDepth = 1
81+
assert(not iddfs:findPath(start, goal, 1))
82+
end)
83+
--]]
84+
print(('-'):rep(80))
85+
print(('Total : %02d: Pass: %02d - Failed : %02d - Success: %.2f %%')
86+
:format(total, pass, total-pass, (pass*100/total)))
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
-- A linear boundless 1D space map handler
2+
3+
-- This handler is devised for 1-directional continuous space, where
4+
-- nodes are represented with consecutive integer values: 0, 1, 2, etc.
5+
-- Similar to a range (interval) with no bounds.
6+
-- i.e., the path from 5 to 0 would be 5, 4, 3, 2, 1, 0.
7+
8+
-- Implements Node class (from node.lua)
9+
local Node = require 'node'
10+
function Node:initialize(value) self.value = value end
11+
function Node:toString() return ('Node: %d'):format(self.value) end
12+
function Node:isEqualTo(n) return self.value == n.value end
13+
14+
-- Finds a specific node in an array
15+
local function array_find(array, value)
16+
for i, v in ipairs(array) do
17+
if v.value == value then return v end
18+
end
19+
end
20+
21+
-- Internal cache for all nodes
22+
local nodes = {}
23+
local limits = {}
24+
25+
-- Handler implementation
26+
local handler = {}
27+
28+
-- Inits the search space with bounds
29+
function handler.init(from, to)
30+
limits.low, limits.high = from, to
31+
for i = from, to do
32+
table.insert(nodes, Node(i))
33+
end
34+
end
35+
36+
-- Creates and returns a Node
37+
function handler.getNode(value) return array_find(nodes, value) end
38+
39+
-- Returns an array of all nodes in the graph
40+
function handler.getAllNodes() return nodes end
41+
42+
-- Returns the distance between node a and node b
43+
function handler.distance(a, b) return math.abs(a.value - b.value) end
44+
45+
-- Returns an array of neighbors of node n
46+
function handler.getNeighbors(n)
47+
local neighbors = {}
48+
if n.value - 1 >= limits.low then
49+
table.insert(neighbors, handler.getNode(n.value - 1))
50+
end
51+
if n.value + 1 <= limits.high then
52+
table.insert(neighbors, handler.getNode(n.value + 1))
53+
end
54+
return neighbors
55+
end
56+
57+
return handler
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
-- Node class abstraction
2+
3+
-- This class represents an abstraction of a Node.
4+
-- It should implemented with additional functions when
5+
-- writing a custom handler.
6+
7+
-- See dfs.lua comment header.
8+
-- See custom handlers for reference (*_hander.lua).
9+
10+
local class = require 'class'
11+
12+
local Node = class ()
13+
Node.__eq = function(a, b) return a:isEqualTo(b) end
14+
Node.__tostring = function(n) return n:toString() end
15+
16+
return Node

0 commit comments

Comments
 (0)