graphsrb 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.
- checksums.yaml +7 -0
- data/.DS_Store +0 -0
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/.travis.yml +7 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +37 -0
- data/LICENSE.txt +21 -0
- data/README.md +338 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/examples/ApproxAlgs/TSP/tsp.rb +45 -0
- data/examples/ApproxAlgs/VertexCover/main.rb +34 -0
- data/examples/ApproxAlgs/VertexCover/vertex_cover.rb +31 -0
- data/examples/Apts/artc_points.rb +59 -0
- data/examples/Apts/main.rb +51 -0
- data/examples/BackEdges/backedges.rb +37 -0
- data/examples/BackEdges/main.rb +41 -0
- data/examples/BipartMatching/bipart_matching.rb +34 -0
- data/examples/BipartMatching/main.rb +58 -0
- data/examples/EdgeConnectivity/econ.rb +31 -0
- data/examples/EdgeConnectivity/main.rb +34 -0
- data/examples/Euler/euler_tour.rb +36 -0
- data/examples/Euler/main.rb +19 -0
- data/examples/FundCircuits/fund_circuits.rb +49 -0
- data/examples/FundCircuits/main.rb +34 -0
- data/examples/FundCutsets/fund_cutsets.rb +50 -0
- data/examples/FundCutsets/main.rb +26 -0
- data/examples/MaxFlow/edmonds_karp.rb +91 -0
- data/examples/MaxFlow/main.rb +79 -0
- data/examples/PrimMST/main.rb +46 -0
- data/examples/PrimMST/prim.rb +82 -0
- data/examples/ProbAlgs/LargeCustset/large_cutset.rb +59 -0
- data/examples/ProbAlgs/LargeCustset/main.rb +45 -0
- data/graphsrb.gemspec +39 -0
- data/lib/graphsrb.rb +11 -0
- data/lib/graphsrb/adjacency_list.rb +78 -0
- data/lib/graphsrb/base_graph.rb +167 -0
- data/lib/graphsrb/diedge.rb +6 -0
- data/lib/graphsrb/digraph.rb +93 -0
- data/lib/graphsrb/edge.rb +34 -0
- data/lib/graphsrb/exceptions.rb +5 -0
- data/lib/graphsrb/graph.rb +46 -0
- data/lib/graphsrb/node.rb +21 -0
- data/lib/graphsrb/version.rb +3 -0
- data/lib/graphsrb/vertex.rb +32 -0
- metadata +149 -0
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'graphsrb'
|
2
|
+
include Graphsrb
|
3
|
+
|
4
|
+
#Assumption: graph is nonempty connected undirected
|
5
|
+
module PrimMST
|
6
|
+
INF = (2**(0.size * 8 -2) -1)
|
7
|
+
|
8
|
+
def self.run(graph)
|
9
|
+
init(graph)
|
10
|
+
|
11
|
+
start = graph.vertices.first
|
12
|
+
include_vertex(start)
|
13
|
+
|
14
|
+
adj_vertices = graph.adjacent_vertices(start)
|
15
|
+
adj_vertices.each do |v|
|
16
|
+
@dist[v.id] = graph.edge(start, v).weight
|
17
|
+
@parent[v.id] = start
|
18
|
+
end
|
19
|
+
|
20
|
+
while not all_vertices_included?
|
21
|
+
v = min_dist_vertex
|
22
|
+
include_vertex(v)
|
23
|
+
update_distances(v)
|
24
|
+
end
|
25
|
+
|
26
|
+
edges = []
|
27
|
+
(0...@parent.size).each do |i|
|
28
|
+
edges << graph.edge(@parent[i], Vertex.new(i)) if @parent[i]
|
29
|
+
end
|
30
|
+
edges
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
def self.init(g)
|
35
|
+
@graph = g
|
36
|
+
@vertex_count = g.vertex_count
|
37
|
+
@included_vertex_count = 0
|
38
|
+
@included = []
|
39
|
+
@dist = []
|
40
|
+
@parent = []
|
41
|
+
@graph.vertices.each do |v|
|
42
|
+
@included[v.id] = false
|
43
|
+
@dist[v.id] = INF
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.included?(v)
|
48
|
+
@included[v.id]
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.include_vertex(v)
|
52
|
+
@included[v.id] = true
|
53
|
+
@included_vertex_count += 1
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.update_distances(v)
|
57
|
+
vertices = @graph.adjacent_vertices(v)
|
58
|
+
vertices.each do |u|
|
59
|
+
edge = @graph.edge(v,u)
|
60
|
+
if !included?(u) && (@dist[u.id] > edge.weight)
|
61
|
+
@dist[u.id] = edge.weight
|
62
|
+
@parent[u.id] = v
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.all_vertices_included?
|
68
|
+
@included_vertex_count == @vertex_count
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.min_dist_vertex
|
72
|
+
min = INF
|
73
|
+
vertex = nil
|
74
|
+
@graph.vertices.each do |v|
|
75
|
+
if @dist[v.id] < min && !included?(v)
|
76
|
+
min = @dist[v.id]
|
77
|
+
vertex = v
|
78
|
+
end
|
79
|
+
end
|
80
|
+
vertex
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'graphsrb'
|
2
|
+
include Graphsrb
|
3
|
+
|
4
|
+
# The problem is finding a large cutset in
|
5
|
+
# a simple undirected graph where each edge
|
6
|
+
# has weigh equal to 1.
|
7
|
+
# This problem is NP-hard.
|
8
|
+
module LargeCutset
|
9
|
+
|
10
|
+
def self.run(graph)
|
11
|
+
init(graph)
|
12
|
+
compute_cut
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.init(graph)
|
16
|
+
@graph = graph
|
17
|
+
@pA, @pB = [], []
|
18
|
+
end
|
19
|
+
|
20
|
+
#Monte Carlo algorithm
|
21
|
+
def self.compute_cut
|
22
|
+
prng = Random.new(Time.now.to_i)
|
23
|
+
@graph.vertices.each do |v|
|
24
|
+
if prng.rand(100) < 50
|
25
|
+
@pA << v
|
26
|
+
else
|
27
|
+
@pB << v
|
28
|
+
end
|
29
|
+
end
|
30
|
+
cut_weight
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.cut_weight
|
34
|
+
total_weight = 0
|
35
|
+
@pA.each do |v|
|
36
|
+
@graph.adjacent_vertices(v).each do |u|
|
37
|
+
total_weight += 1 if @pB.include?(u)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
total_weight
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.print_cutset
|
44
|
+
puts "Cutset:"
|
45
|
+
@pA.each do |v|
|
46
|
+
@graph.adjacent_vertices(v).each do |u|
|
47
|
+
puts @graph.edge(v,u) if @pB.include?(u)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.print_partitions
|
53
|
+
print "Partition A: "
|
54
|
+
@pA.each{|v| print "#{v} "}
|
55
|
+
print "\nPartition B: "
|
56
|
+
@pB.each{|v| print "#{v} "}
|
57
|
+
print "\n"
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require_relative 'large_cutset'
|
2
|
+
|
3
|
+
def run_example(example_title, graph)
|
4
|
+
puts "------- #{example_title} ------ "
|
5
|
+
puts "The graph has #{graph.vertex_count} vertices and #{graph.edge_count} edges"
|
6
|
+
puts "Cutset size #{LargeCutset.run(graph)}"
|
7
|
+
LargeCutset.print_partitions
|
8
|
+
LargeCutset.print_cutset
|
9
|
+
end
|
10
|
+
|
11
|
+
|
12
|
+
def main
|
13
|
+
edges = [
|
14
|
+
[1,2], [1,3], [1,5],
|
15
|
+
[2,4], [2,5], [2,6],
|
16
|
+
[3,4], [3,6],
|
17
|
+
[4,5], [4,6],
|
18
|
+
[5,6]
|
19
|
+
]
|
20
|
+
run_example('Example 1', Graph.new(edges:edges))
|
21
|
+
|
22
|
+
edges = [
|
23
|
+
[1,2], [1,3],
|
24
|
+
[2,4],
|
25
|
+
[3,4],
|
26
|
+
[4,5],
|
27
|
+
[5,6], [5,6],
|
28
|
+
[6,7]
|
29
|
+
]
|
30
|
+
run_example('Example 2', Graph.new(edges:edges))
|
31
|
+
|
32
|
+
edges = [[1,2],[2,3],[3,4],[4,1],[1,3],[4,2]]
|
33
|
+
run_example('Example 3', Graph.new(edges:edges))
|
34
|
+
|
35
|
+
edges = [
|
36
|
+
[1,2], [1,3], [1,4], [1,5],
|
37
|
+
[2,3], [2,4], [2,5],
|
38
|
+
[3,4], [3,5],
|
39
|
+
[4,5]
|
40
|
+
]
|
41
|
+
run_example('Example 4', Graph.new(edges:edges))
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
main
|
data/graphsrb.gemspec
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require "graphsrb/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "graphsrb"
|
8
|
+
spec.version = Graphsrb::VERSION
|
9
|
+
spec.authors = ["Bayram Kuliyev"]
|
10
|
+
spec.email = ["bkuliyev@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{This gem allows to create simple directed and undirected graphs. Basic operations allows easily implement graph algorithms}
|
13
|
+
spec.homepage = "https://github.com/fade2black/graphsrb"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
|
17
|
+
# to allow pushing to a single host or delete this section to allow pushing to any host.
|
18
|
+
if spec.respond_to?(:metadata)
|
19
|
+
spec.metadata['allowed_push_host'] = "https://rubygems.org"
|
20
|
+
else
|
21
|
+
raise "RubyGems 2.0 or newer is required to protect against " \
|
22
|
+
"public gem pushes."
|
23
|
+
end
|
24
|
+
|
25
|
+
# Specify which files should be added to the gem when it is released.
|
26
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
27
|
+
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
28
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
29
|
+
end
|
30
|
+
spec.bindir = "exe"
|
31
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
32
|
+
spec.require_paths = ["lib"]
|
33
|
+
|
34
|
+
spec.add_development_dependency "bundler", "~> 1.16"
|
35
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
36
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
37
|
+
|
38
|
+
spec.add_dependency "json", "~> 1.8"
|
39
|
+
end
|
data/lib/graphsrb.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
module Graphsrb; end
|
2
|
+
require "graphsrb/version"
|
3
|
+
require "graphsrb/exceptions"
|
4
|
+
require "graphsrb/vertex"
|
5
|
+
require "graphsrb/node"
|
6
|
+
require "graphsrb/adjacency_list"
|
7
|
+
require "graphsrb/edge"
|
8
|
+
require "graphsrb/base_graph"
|
9
|
+
require "graphsrb/diedge"
|
10
|
+
require "graphsrb/digraph"
|
11
|
+
require "graphsrb/graph"
|
@@ -0,0 +1,78 @@
|
|
1
|
+
|
2
|
+
class Graphsrb::AdjacencyList
|
3
|
+
include Enumerable
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@adj_list = []
|
7
|
+
end
|
8
|
+
|
9
|
+
#Adds a node to the adjacency list
|
10
|
+
def add(node)
|
11
|
+
adj_list << node.clone
|
12
|
+
end
|
13
|
+
|
14
|
+
#Adds a node to the adjacency list
|
15
|
+
def <<(node)
|
16
|
+
add(node)
|
17
|
+
end
|
18
|
+
|
19
|
+
#Updates weight
|
20
|
+
def update_weight(node, w)
|
21
|
+
node = find(node)
|
22
|
+
node.update_weight(w) unless node.nil?
|
23
|
+
end
|
24
|
+
|
25
|
+
#Increses weight by +dw+
|
26
|
+
def increase_weight(node, dw)
|
27
|
+
node = find(node)
|
28
|
+
node.update_weight(node.weight + dw) unless node.nil?
|
29
|
+
end
|
30
|
+
|
31
|
+
def each(&block)
|
32
|
+
@adj_list.each(&block)
|
33
|
+
end
|
34
|
+
|
35
|
+
#Returns the number of nodes in the adjacency list
|
36
|
+
def size
|
37
|
+
adj_list.size
|
38
|
+
end
|
39
|
+
|
40
|
+
#Removes a node from the adjacency list
|
41
|
+
def delete(node)
|
42
|
+
adj_list.delete(node)
|
43
|
+
end
|
44
|
+
|
45
|
+
#Searches for a node in the adjacency list
|
46
|
+
#Returns nil if not found
|
47
|
+
def find(node)
|
48
|
+
index = adj_list.index node
|
49
|
+
if index.nil?
|
50
|
+
return nil
|
51
|
+
else
|
52
|
+
adj_list[index]
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
#Returns all nodes
|
57
|
+
def nodes
|
58
|
+
adj_list.clone
|
59
|
+
end
|
60
|
+
|
61
|
+
#Returns true if the adjacency list contains the node, false otherwise
|
62
|
+
def has_node?(node)
|
63
|
+
not find(node).nil?
|
64
|
+
end
|
65
|
+
|
66
|
+
#Remove all nodes from the list
|
67
|
+
def clear
|
68
|
+
adj_list.clear
|
69
|
+
end
|
70
|
+
|
71
|
+
#Creates and returns the created node
|
72
|
+
def self.create_node(vertex_id, args={})
|
73
|
+
Graphsrb::Node.new(vertex_id, args)
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
attr_reader :adj_list
|
78
|
+
end
|
@@ -0,0 +1,167 @@
|
|
1
|
+
|
2
|
+
#This class is the base clase for undirected and directed graphs.
|
3
|
+
class Graphsrb::BaseGraph
|
4
|
+
|
5
|
+
def initialize(args={})
|
6
|
+
@adj_table = {}
|
7
|
+
args.fetch(:vertices,[]).each{|vertex_id| adj_table[vertex_id] = _create_adjacency_list }
|
8
|
+
args.fetch(:edges,[]).each do |e|
|
9
|
+
unless has_edge?(e[0], e[1])
|
10
|
+
vertices_must_be_different!(e[0], e[1])
|
11
|
+
add_edge(e[0], e[1], weight: e[2] || 1)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
#Returns vertices of the graph
|
17
|
+
def vertices
|
18
|
+
vertex_array = []
|
19
|
+
adj_table.keys.each do |id|
|
20
|
+
vertex_array << _create_vertex(id)
|
21
|
+
end
|
22
|
+
vertex_array
|
23
|
+
end
|
24
|
+
|
25
|
+
#Returns an edge
|
26
|
+
def edge(v, u)
|
27
|
+
id1, id2 = v.id, u.id
|
28
|
+
if has_vertex?(id1)
|
29
|
+
node = adj_table[id1].find(_create_node(id2))
|
30
|
+
return _create_edge(id1, id2, weight:node.weight) if node
|
31
|
+
end
|
32
|
+
|
33
|
+
if has_vertex?(id2)
|
34
|
+
node = adj_table[id2].find(_create_node(id1))
|
35
|
+
return _create_edge(id1, id2, weight:node.weight) if node
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
#Updates edge weight
|
40
|
+
def update_weight(v, u, w)
|
41
|
+
id1, id2 = v.id, u.id
|
42
|
+
adj_table[id1].update_weight(_create_node(id2), w) if has_vertex?(id1)
|
43
|
+
adj_table[id2].update_weight(_create_node(id1), w) if has_vertex?(id2)
|
44
|
+
end
|
45
|
+
|
46
|
+
#Increases edge weight by +dw+
|
47
|
+
def increase_weight(v, u, dw)
|
48
|
+
id1, id2 = v.id, u.id
|
49
|
+
adj_table[id1].increase_weight(_create_node(id2), dw) if has_vertex?(id1)
|
50
|
+
adj_table[id2].increase_weight(_create_node(id1), dw) if has_vertex?(id2)
|
51
|
+
end
|
52
|
+
|
53
|
+
#Returns edges of the graph
|
54
|
+
def edges
|
55
|
+
edges_array = []
|
56
|
+
vertices.each do |vertex|
|
57
|
+
adj_table[vertex.id].each do |node|
|
58
|
+
edges_array << _create_edge(vertex.id, node.vertex.id, weight: node.weight)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
edges_array
|
62
|
+
end
|
63
|
+
|
64
|
+
#Removes all vertices and edges.
|
65
|
+
def clear
|
66
|
+
adj_table.each_value{|list| list.clear}
|
67
|
+
@adj_table = {}
|
68
|
+
true
|
69
|
+
end
|
70
|
+
|
71
|
+
##Creates a copy of the graph
|
72
|
+
def copy
|
73
|
+
Marshal::load(Marshal.dump(self))
|
74
|
+
end
|
75
|
+
|
76
|
+
#Returns the number of vertices
|
77
|
+
def vertex_count
|
78
|
+
vertices.size
|
79
|
+
end
|
80
|
+
|
81
|
+
#Returns the number of edges in the graph
|
82
|
+
def edge_count
|
83
|
+
sum = 0
|
84
|
+
adj_table.each_value{|list| sum += list.size}
|
85
|
+
sum
|
86
|
+
end
|
87
|
+
|
88
|
+
#Checks whether the graph has a vertex
|
89
|
+
def has_vertex?(id)
|
90
|
+
not adj_table[id].nil?
|
91
|
+
end
|
92
|
+
|
93
|
+
alias vertex? has_vertex?
|
94
|
+
|
95
|
+
#Checks whether the graph has an edge
|
96
|
+
def has_edge?(id1, id2)
|
97
|
+
has_vertex?(id1) && adj_table[id1].has_node?(_create_node(id2)) ||
|
98
|
+
has_vertex?(id2) && adj_table[id2].has_node?(_create_node(id1))
|
99
|
+
end
|
100
|
+
|
101
|
+
alias edge? has_edge?
|
102
|
+
|
103
|
+
#Adds a new vertex
|
104
|
+
def add_vertex(id)
|
105
|
+
return nil if has_vertex?(id)
|
106
|
+
adj_table[id] = _create_adjacency_list
|
107
|
+
true
|
108
|
+
end
|
109
|
+
|
110
|
+
#Adds a new edge
|
111
|
+
def add_edge(id1, id2, args={})
|
112
|
+
vertices_must_be_different!(id1, id2)
|
113
|
+
return nil if has_edge?(id1, id2)
|
114
|
+
add_vertex(id1) unless has_vertex?(id1)
|
115
|
+
add_vertex(id2) unless has_vertex?(id2)
|
116
|
+
adj_table[id1] << _create_node(id2, args)
|
117
|
+
true
|
118
|
+
end
|
119
|
+
|
120
|
+
#Remove an edge from the graph
|
121
|
+
def remove_edge(v1, v2)
|
122
|
+
id1, id2 = v1.id, v2.id
|
123
|
+
adj_table[id1].delete(_create_node(id2)) if has_vertex?(id1)
|
124
|
+
adj_table[id2].delete(_create_node(id1)) if has_vertex?(id2)
|
125
|
+
true
|
126
|
+
end
|
127
|
+
|
128
|
+
#Remove a vertex from the graph
|
129
|
+
def remove_vertex(v)
|
130
|
+
id = v.id
|
131
|
+
return nil unless has_vertex?(id)
|
132
|
+
|
133
|
+
adj_table[id].clear
|
134
|
+
adj_table.delete(id)
|
135
|
+
|
136
|
+
node = _create_node(id)
|
137
|
+
adj_table.each_value{|list| list.delete(node)}
|
138
|
+
return v
|
139
|
+
end
|
140
|
+
|
141
|
+
|
142
|
+
protected
|
143
|
+
attr_reader :adj_table
|
144
|
+
|
145
|
+
def _create_node(vertex_id, args={})
|
146
|
+
Graphsrb::Node.new(vertex_id, args)
|
147
|
+
end
|
148
|
+
|
149
|
+
def _create_adjacency_list
|
150
|
+
Graphsrb::AdjacencyList.new
|
151
|
+
end
|
152
|
+
|
153
|
+
def _create_edge(id1, id2, args={})
|
154
|
+
Graphsrb::Edge.new(id1, id2, args)
|
155
|
+
end
|
156
|
+
|
157
|
+
def _create_vertex(id)
|
158
|
+
Graphsrb::Vertex.new(id)
|
159
|
+
end
|
160
|
+
|
161
|
+
def vertices_must_be_different!(id1, id2)
|
162
|
+
if id1 == id2
|
163
|
+
raise Graphsrb::EdgeInitializationError, "Vertex id's must be different from each other"
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
end
|