usearchtree 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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: []
|