usearchtree 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/usearchtree.rb +8 -0
- data/lib/usearchtree/breadth.rb +22 -0
- data/lib/usearchtree/depth.rb +23 -0
- data/lib/usearchtree/graph.rb +68 -0
- data/lib/usearchtree/graphloader.rb +41 -0
- data/lib/usearchtree/node.rb +66 -0
- data/lib/usearchtree/search.rb +58 -0
- data/lib/usearchtree/uniform.rb +24 -0
- metadata +53 -0
data/lib/usearchtree.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
class BreadthFirstSearch < SearchAlgorithm
|
2
|
+
def search
|
3
|
+
@list = [@start]
|
4
|
+
until @list.empty?
|
5
|
+
@history << @list.clone
|
6
|
+
parent = @list.shift
|
7
|
+
@traversal << parent
|
8
|
+
@cache[parent.key] = parent
|
9
|
+
if parent == @goal
|
10
|
+
return
|
11
|
+
end
|
12
|
+
parent.edges.each do |edge|
|
13
|
+
child = edge.node
|
14
|
+
if @cache and not @cache.has_key?(child.key)
|
15
|
+
@cache[child.key] = child
|
16
|
+
@tree[child] = parent
|
17
|
+
@list << child
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
class DepthFirstSearch < SearchAlgorithm
|
2
|
+
def search
|
3
|
+
@list = [@start]
|
4
|
+
until @list.empty?
|
5
|
+
@history << @list.clone
|
6
|
+
parent_node = @list.pop
|
7
|
+
@traversal << parent_node
|
8
|
+
@cache[parent_node.key] = parent_node
|
9
|
+
if parent_node == @goal
|
10
|
+
return
|
11
|
+
end
|
12
|
+
parent_node.edges.reverse_each do |edge|
|
13
|
+
node = edge.node
|
14
|
+
cost = edge.cost
|
15
|
+
if not @cache or not @cache.has_key? node.key
|
16
|
+
@cache[node.key] = node
|
17
|
+
@tree[node] = parent_node
|
18
|
+
@list.push node
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# Represents any weighted, directed graph.
|
2
|
+
#
|
3
|
+
# Author: Johnny Lee Othon
|
4
|
+
class Graph
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@nodes = Array.new
|
8
|
+
@next_id = 0
|
9
|
+
end
|
10
|
+
|
11
|
+
# Returns the list of nodes
|
12
|
+
def to_s
|
13
|
+
@nodes.to_s
|
14
|
+
end
|
15
|
+
|
16
|
+
# Returns the graph as a list of adjacencies
|
17
|
+
def to_adjacency_lists
|
18
|
+
if @nodes.empty?
|
19
|
+
"Empty graph"
|
20
|
+
else
|
21
|
+
@nodes.collect{|node| node.to_adjacency_list}.join("\n")
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def node key
|
26
|
+
if key.kind_of? Integer
|
27
|
+
@nodes[key]
|
28
|
+
else
|
29
|
+
@nodes.find{|n| n.name == key}
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Adds a node to the graph and optionally sets a name.
|
34
|
+
def add_node name=nil
|
35
|
+
@nodes << Node.new(self.next_id, name)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Adds an edge from node i to node j. Where i and j can be an index or a
|
39
|
+
# name.
|
40
|
+
def add_edge i, j, cost
|
41
|
+
self.node(i).add_edge self.node(j), cost
|
42
|
+
end
|
43
|
+
|
44
|
+
# Labels a single node with index i. Fails if the node does not exist.
|
45
|
+
def label_node i, name
|
46
|
+
@nodes[i].name = name
|
47
|
+
end
|
48
|
+
|
49
|
+
# Gets the number of nodes in the graph
|
50
|
+
def length
|
51
|
+
@nodes.length
|
52
|
+
end
|
53
|
+
|
54
|
+
# Gets the cost from node at i to node at j.
|
55
|
+
def cost i, j
|
56
|
+
nodeI = (i.kind_of? Node) ? i : self.node(i)
|
57
|
+
nodeJ = (j.kind_of? Node) ? j : self.node(j)
|
58
|
+
nodeI.cost(nodeJ)
|
59
|
+
end
|
60
|
+
|
61
|
+
# Gets and increases the next id.
|
62
|
+
def next_id
|
63
|
+
id = @next_id
|
64
|
+
@next_id += 1
|
65
|
+
id
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# Consumes a node_labels.txt file
|
2
|
+
# e.g. http://people.sc.fsu.edu/~jburkardt%20/data/graph_representation/mst_node_labels.txt
|
3
|
+
def load_label_nodes graph, file
|
4
|
+
File.open(file, 'r') do |f|
|
5
|
+
f.lazy.reject do |line|
|
6
|
+
line.chomp.empty? or line.start_with? '#'
|
7
|
+
end.with_index do |line, i|
|
8
|
+
line.chomp!
|
9
|
+
if not line.empty? and not line.start_with? '#'
|
10
|
+
unless graph.length > i
|
11
|
+
graph.add_node
|
12
|
+
end
|
13
|
+
graph.label_node(i, line)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
return graph
|
18
|
+
end
|
19
|
+
|
20
|
+
# Loads a distance matrix. Rows correspond to source nodes and columns
|
21
|
+
# correspond to destination nodes. -1 indicates a non-connection, 0 indicates
|
22
|
+
# the same node, and positive values indicate an edge.
|
23
|
+
def load_distance_matrix graph, file
|
24
|
+
File.open(file, 'r') do |f|
|
25
|
+
f.each.reject do |line|
|
26
|
+
line.empty? or line.start_with? '#'
|
27
|
+
end.each.with_index do |line, i|
|
28
|
+
costs = line.split
|
29
|
+
costs = costs.collect &:to_i
|
30
|
+
costs.each.with_index do |cost, j|
|
31
|
+
unless graph.length > j
|
32
|
+
graph.add_node
|
33
|
+
end
|
34
|
+
unless cost.zero? or cost == -1
|
35
|
+
graph.add_edge i, j, cost
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
return graph
|
41
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# Represents to a node in the graph
|
2
|
+
#
|
3
|
+
# Author: Johnny Lee Othon
|
4
|
+
class Node
|
5
|
+
|
6
|
+
attr_accessor :id, :name
|
7
|
+
|
8
|
+
include Comparable
|
9
|
+
|
10
|
+
def initialize id, name=nil
|
11
|
+
@name = name
|
12
|
+
@id = id
|
13
|
+
@edges = Array.new
|
14
|
+
end
|
15
|
+
|
16
|
+
# Returns the name of the node or its id as a string.
|
17
|
+
def to_s
|
18
|
+
@name or "#{@id}"
|
19
|
+
end
|
20
|
+
|
21
|
+
# Returns the name of the node or its id as a string.
|
22
|
+
def inspect
|
23
|
+
self.to_s
|
24
|
+
end
|
25
|
+
|
26
|
+
def <=> other
|
27
|
+
return @id <=> other.id
|
28
|
+
end
|
29
|
+
|
30
|
+
# Returns the node in the format <code><id:name></code>.
|
31
|
+
def to_adjacency_list
|
32
|
+
s = "#{name}"
|
33
|
+
unless @name.nil? or @name.empty?
|
34
|
+
s += "(#{@id})"
|
35
|
+
end
|
36
|
+
unless @edges.empty?
|
37
|
+
s += ": "
|
38
|
+
end
|
39
|
+
nodes = @edges.map do |edge|
|
40
|
+
"#{edge.node}=>#{edge.cost}"
|
41
|
+
end.join(", ")
|
42
|
+
s += nodes
|
43
|
+
end
|
44
|
+
|
45
|
+
def add_edge node, cost
|
46
|
+
@edges << Edge.new(node, cost)
|
47
|
+
end
|
48
|
+
|
49
|
+
def cost node
|
50
|
+
edge = @edges.find{|e| e.node == node}
|
51
|
+
if edge
|
52
|
+
edge.cost
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Gets the name if exists or the id
|
57
|
+
def key
|
58
|
+
@name or @id
|
59
|
+
end
|
60
|
+
|
61
|
+
# Gets an enumerator of the edges
|
62
|
+
def edges
|
63
|
+
return @edges.each
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# Author: Johnny Lee Othon
|
2
|
+
class SearchAlgorithm
|
3
|
+
|
4
|
+
attr_reader :goal, :history, :space, :start, :traversal, :tree
|
5
|
+
|
6
|
+
# Initializes a search algorithm.
|
7
|
+
# Parameters:
|
8
|
+
# [space] a graph
|
9
|
+
# [start] the id or name of the starting node
|
10
|
+
# [goal] the id or name of the goal node
|
11
|
+
# [cache] <code>true</code> a cache of visited nodes is desired
|
12
|
+
def initialize space, start, goal, caches=true
|
13
|
+
@space = space
|
14
|
+
@start = space.node(start)
|
15
|
+
@goal = space.node(goal)
|
16
|
+
# node.key => node
|
17
|
+
@cache = (Hash.new if caches)
|
18
|
+
# order in which the graph nodes were traversed
|
19
|
+
@traversal = Array.new
|
20
|
+
# parent => parent_node
|
21
|
+
@tree = {@start => nil}
|
22
|
+
# lazy list of nodes from start to goal
|
23
|
+
@path = Array.new
|
24
|
+
# lazy total of cost from start to goal
|
25
|
+
@cost = nil
|
26
|
+
# history to see the history of the stack
|
27
|
+
@history = Array.new
|
28
|
+
end
|
29
|
+
|
30
|
+
# Performs the search and populates <code>traversal</code> and
|
31
|
+
# <code>tree</code>
|
32
|
+
def search
|
33
|
+
throw "Unimplemented"
|
34
|
+
end
|
35
|
+
|
36
|
+
# Returns a list of all nodes in order from start to goal.
|
37
|
+
def path
|
38
|
+
return @path if @path.any?
|
39
|
+
node = @goal
|
40
|
+
@cost = 0
|
41
|
+
while node
|
42
|
+
parent = @tree[node]
|
43
|
+
@path.insert(0, node)
|
44
|
+
@cost += parent ? parent.cost(node) : 0
|
45
|
+
node = parent
|
46
|
+
end
|
47
|
+
return @path
|
48
|
+
end
|
49
|
+
|
50
|
+
# Retruns the total cost from start to goal following the path specifed in
|
51
|
+
# <code>SearchAlgorithm#Path</code>.
|
52
|
+
def cost
|
53
|
+
self.path if @path.empty?
|
54
|
+
return @cost
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
class UniformCostSearch < SearchAlgorithm
|
2
|
+
def search
|
3
|
+
@list = [[0, @start]]
|
4
|
+
until @list.empty?
|
5
|
+
@history << @list.clone
|
6
|
+
cost, parent_node = @list.shift
|
7
|
+
@cache[parent_node.key] = parent_node.key
|
8
|
+
@traversal << [cost, parent_node]
|
9
|
+
return if parent_node == @goal
|
10
|
+
pcost = cost
|
11
|
+
parent_node.edges.each do |edge|
|
12
|
+
node = edge.node
|
13
|
+
cost = edge.cost + pcost
|
14
|
+
if not @cache or not @cache.has_key? node.key
|
15
|
+
@cache[node.key] = node
|
16
|
+
unless @tree.has_key? node
|
17
|
+
@tree[node] = parent_node
|
18
|
+
end
|
19
|
+
@list << [cost, node]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
metadata
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: usearchtree
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Johnny Lee Othon
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2015-02-14 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: Uninformed search trees!
|
15
|
+
email: jleeothon@gmail.com
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- lib/usearchtree.rb
|
21
|
+
- lib/usearchtree/breadth.rb
|
22
|
+
- lib/usearchtree/depth.rb
|
23
|
+
- lib/usearchtree/graph.rb
|
24
|
+
- lib/usearchtree/graphloader.rb
|
25
|
+
- lib/usearchtree/node.rb
|
26
|
+
- lib/usearchtree/search.rb
|
27
|
+
- lib/usearchtree/uniform.rb
|
28
|
+
homepage: https://github.com/jleeothon/usearchtree
|
29
|
+
licenses:
|
30
|
+
- MIT
|
31
|
+
post_install_message:
|
32
|
+
rdoc_options: []
|
33
|
+
require_paths:
|
34
|
+
- lib
|
35
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
36
|
+
none: false
|
37
|
+
requirements:
|
38
|
+
- - ! '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - ! '>='
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0'
|
47
|
+
requirements: []
|
48
|
+
rubyforge_project:
|
49
|
+
rubygems_version: 1.8.23
|
50
|
+
signing_key:
|
51
|
+
specification_version: 3
|
52
|
+
summary: Uninformed search trees!
|
53
|
+
test_files: []
|