networkr 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.rspec +2 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +43 -0
- data/LICENSE.txt +21 -0
- data/README.md +46 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/networkr.rb +13 -0
- data/lib/networkr/algorithms/dijkstra.rb +35 -0
- data/lib/networkr/algorithms/karger.rb +43 -0
- data/lib/networkr/algorithms/kosaraju.rb +88 -0
- data/lib/networkr/algorithms/prim.rb +36 -0
- data/lib/networkr/graphs/digraph.rb +108 -0
- data/lib/networkr/graphs/graph.rb +183 -0
- data/lib/networkr/graphs/multigraph.rb +100 -0
- data/lib/networkr/version.rb +3 -0
- data/lib/utils/heap.rb +111 -0
- data/networkr.gemspec +33 -0
- metadata +105 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 43e672ad355d6a2b7535dcea503b62266cc29d8c
|
4
|
+
data.tar.gz: 054fb810a76cc380e31f6c51a184d798538106d5
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 3f623b282a654a2ec6b18d4e8ab4cf2e05c2839c82c48da0e830ddfbc0cc8c2ad5c916ad609fd5835a189364df528e5f0a558e4e2d463681fcc6565c14193795
|
7
|
+
data.tar.gz: f9dbe521e03f73414c12b03fc7d7ab7550a98318f26b03f19283af502e59636a224d4d2854ebbbf51a14088390ad33268b301f916adaad385668c9e3f9853cac
|
data/.rspec
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
networkr (0.0.1)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: https://rubygems.org/
|
8
|
+
specs:
|
9
|
+
coderay (1.1.1)
|
10
|
+
diff-lcs (1.2.5)
|
11
|
+
method_source (0.8.2)
|
12
|
+
pry (0.10.3)
|
13
|
+
coderay (~> 1.1.0)
|
14
|
+
method_source (~> 0.8.1)
|
15
|
+
slop (~> 3.4)
|
16
|
+
rake (10.4.2)
|
17
|
+
rspec (3.4.0)
|
18
|
+
rspec-core (~> 3.4.0)
|
19
|
+
rspec-expectations (~> 3.4.0)
|
20
|
+
rspec-mocks (~> 3.4.0)
|
21
|
+
rspec-core (3.4.4)
|
22
|
+
rspec-support (~> 3.4.0)
|
23
|
+
rspec-expectations (3.4.0)
|
24
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
25
|
+
rspec-support (~> 3.4.0)
|
26
|
+
rspec-mocks (3.4.1)
|
27
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
28
|
+
rspec-support (~> 3.4.0)
|
29
|
+
rspec-support (3.4.1)
|
30
|
+
slop (3.6.0)
|
31
|
+
|
32
|
+
PLATFORMS
|
33
|
+
ruby
|
34
|
+
|
35
|
+
DEPENDENCIES
|
36
|
+
bundler (~> 1.12)
|
37
|
+
networkr!
|
38
|
+
pry
|
39
|
+
rake (~> 10.0)
|
40
|
+
rspec (~> 3.0)
|
41
|
+
|
42
|
+
BUNDLED WITH
|
43
|
+
1.12.5
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2016 Yi-Ke Peng
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
# Networkr
|
2
|
+
|
3
|
+
Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/networkr`. To experiment with that code, run `bin/console` for an interactive prompt.
|
4
|
+
|
5
|
+
TODO: Delete this and the text above, and describe your gem
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem 'networkr'
|
13
|
+
```
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
$ bundle
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
$ gem install networkr
|
22
|
+
|
23
|
+
## Usage
|
24
|
+
|
25
|
+
TODO: Write usage instructions here
|
26
|
+
|
27
|
+
## Development
|
28
|
+
|
29
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
30
|
+
|
31
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
32
|
+
|
33
|
+
## TODO
|
34
|
+
more robust tests
|
35
|
+
use heap
|
36
|
+
more flexibility
|
37
|
+
add algorithms
|
38
|
+
|
39
|
+
## Contributing
|
40
|
+
|
41
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/ykpeng/networkr.
|
42
|
+
|
43
|
+
|
44
|
+
## License
|
45
|
+
|
46
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "networkr"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
require "pry"
|
11
|
+
Pry.start
|
12
|
+
|
13
|
+
# require "irb"
|
14
|
+
# IRB.start
|
data/bin/setup
ADDED
data/lib/networkr.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require "networkr/version"
|
2
|
+
|
3
|
+
require "networkr/graphs/graph"
|
4
|
+
require "networkr/graphs/digraph"
|
5
|
+
require "networkr/graphs/multigraph"
|
6
|
+
|
7
|
+
require "networkr/algorithms/dijkstra"
|
8
|
+
require "networkr/algorithms/karger"
|
9
|
+
require "networkr/algorithms/kosaraju"
|
10
|
+
require "networkr/algorithms/prim"
|
11
|
+
|
12
|
+
module Networkr
|
13
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# dijkstra
|
2
|
+
# single-source shortest path
|
3
|
+
|
4
|
+
#Takes undirected, weighted graph, nonnegative weights
|
5
|
+
# O(|V|**2)
|
6
|
+
|
7
|
+
module Networkr
|
8
|
+
class << self
|
9
|
+
def dijkstra(g, source_node)
|
10
|
+
nodes_processed = [source_node]
|
11
|
+
shortest_path_distances = {}
|
12
|
+
shortest_path_distances[source_node] = 0
|
13
|
+
|
14
|
+
while nodes_processed.length != g.length
|
15
|
+
shortest = nil
|
16
|
+
start_node = nil
|
17
|
+
end_node = nil
|
18
|
+
nodes_processed.each do |node|
|
19
|
+
g.children_of(node).each do |v, options|
|
20
|
+
if !nodes_processed.include?(v)
|
21
|
+
if shortest.nil? || shortest_path_distances[node] + options[:weight] < shortest
|
22
|
+
shortest = shortest_path_distances[node] + options[:weight]
|
23
|
+
start_node = node
|
24
|
+
end_node = v
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
nodes_processed << end_node
|
30
|
+
shortest_path_distances[end_node] = shortest
|
31
|
+
end
|
32
|
+
shortest_path_distances
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
=begin
|
2
|
+
Karger's algorithm
|
3
|
+
|
4
|
+
computes a minumum cut of a connected graph
|
5
|
+
|
6
|
+
takes undirected multigraph
|
7
|
+
=end
|
8
|
+
|
9
|
+
module Networkr
|
10
|
+
class << self
|
11
|
+
def karger(g)
|
12
|
+
while g.length > 2
|
13
|
+
node1 = g.adj.keys.sample
|
14
|
+
node2 = g.children_of(node1).keys.sample
|
15
|
+
e = [node1, node2]
|
16
|
+
contract(g, node1, node2)
|
17
|
+
end
|
18
|
+
node1 = g.adj.keys[0]
|
19
|
+
node2 = g.adj.keys[1]
|
20
|
+
g.adj[node1][node2].length
|
21
|
+
end
|
22
|
+
|
23
|
+
def contract(g, node1, node2)
|
24
|
+
g.children_of(node2).each_key do |node|
|
25
|
+
g.add_edge(node1, node) if node != node1
|
26
|
+
g.adj[node].delete(node2)
|
27
|
+
end
|
28
|
+
g.adj.delete(node2)
|
29
|
+
g.nodes.delete(node2)
|
30
|
+
end
|
31
|
+
|
32
|
+
def karger_repeated(g)
|
33
|
+
g_copy = g.deep_copy
|
34
|
+
|
35
|
+
n = g.length
|
36
|
+
mincut = n
|
37
|
+
(n ** 3).times do
|
38
|
+
mincut = karger(g_copy) if karger(g_copy) < mincut
|
39
|
+
end
|
40
|
+
mincut
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require "set"
|
2
|
+
|
3
|
+
# O(V+E), finds strongly connected components of a directed graph
|
4
|
+
|
5
|
+
module Networkr
|
6
|
+
class Tracker
|
7
|
+
attr_accessor :explored, :current_source, :leaders, :current_time,
|
8
|
+
:finishing_times
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@explored = Set.new
|
12
|
+
@current_source = nil
|
13
|
+
@leaders = {}
|
14
|
+
@current_time = 0
|
15
|
+
@finishing_times = {}
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class << self
|
20
|
+
def dfs_loop(graph, tracker)
|
21
|
+
graph.adj.keys.reverse_each do |i|
|
22
|
+
stack = [i]
|
23
|
+
tracker.current_source = i
|
24
|
+
while !stack.empty?
|
25
|
+
v = stack.last
|
26
|
+
if !tracker.explored.include?(v)
|
27
|
+
dfs(graph, stack, v, tracker)
|
28
|
+
else
|
29
|
+
if tracker.finishing_times[v] == nil
|
30
|
+
tracker.leaders[v] = tracker.current_source
|
31
|
+
tracker.current_time += 1
|
32
|
+
tracker.finishing_times[v] = tracker.current_time
|
33
|
+
end
|
34
|
+
stack.pop
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def dfs(graph, stack, v, tracker)
|
41
|
+
tracker.explored << v
|
42
|
+
if !graph.adj[v]
|
43
|
+
tracker.leaders[v] = v
|
44
|
+
tracker.current_time += 1
|
45
|
+
tracker.finishing_times[v] = tracker.current_time
|
46
|
+
stack.pop
|
47
|
+
else
|
48
|
+
graph.adj[v].each_key do |w|
|
49
|
+
if !tracker.explored.include?(w)
|
50
|
+
stack << w
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def kosaraju(original_graph, graph_rev, tracker1, tracker2)
|
57
|
+
dfs_loop(graph_rev, tracker1)
|
58
|
+
sorted_graph_adj = Hash[original_graph.adj.sort_by { |k, v| tracker1.finishing_times[k] }]
|
59
|
+
sorted_graph = DiGraph.new
|
60
|
+
sorted_graph.adj = sorted_graph_adj
|
61
|
+
dfs_loop(sorted_graph, tracker2)
|
62
|
+
tracker2
|
63
|
+
end
|
64
|
+
|
65
|
+
def kosaraju_scc_sizes(original_graph, graph_rev, tracker1, tracker2)
|
66
|
+
tracker2 = kosaraju(original_graph, graph_rev, tracker1, tracker2)
|
67
|
+
sizes(tracker2)
|
68
|
+
end
|
69
|
+
|
70
|
+
def kosaraju_num_scc(original_graph, graph_rev, tracker1, tracker2)
|
71
|
+
tracker2 = kosaraju(original_graph, graph_rev, tracker1, tracker2)
|
72
|
+
num_scc(tracker2)
|
73
|
+
end
|
74
|
+
|
75
|
+
def sizes(tracker2)
|
76
|
+
frequencies = Hash.new(0)
|
77
|
+
tracker2.leaders.each do |k, v|
|
78
|
+
frequencies[v] += 1
|
79
|
+
end
|
80
|
+
|
81
|
+
frequencies.values.sort.reverse
|
82
|
+
end
|
83
|
+
|
84
|
+
def num_scc(tracker2)
|
85
|
+
tracker2.leaders.length
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
=begin
|
2
|
+
Prim's algorithm for calculating minimum spanning tree.
|
3
|
+
|
4
|
+
takes weighted, undirected graph
|
5
|
+
returns tree
|
6
|
+
=end
|
7
|
+
module Networkr
|
8
|
+
class << self
|
9
|
+
def prim(g, start)
|
10
|
+
vertices_spanned = [start]
|
11
|
+
tree_so_far = Graph.new
|
12
|
+
while vertices_spanned.length != g.length
|
13
|
+
e = min_edge(g, vertices_spanned)
|
14
|
+
tree_so_far.add_edge(e[0][0], e[0][1], weight: e[1])
|
15
|
+
vertices_spanned << e[0][1]
|
16
|
+
end
|
17
|
+
tree_so_far
|
18
|
+
end
|
19
|
+
|
20
|
+
def min_edge(g, vertices_spanned)
|
21
|
+
min_edge = nil
|
22
|
+
min_cost = 1.0/0
|
23
|
+
vertices_spanned.each do |u|
|
24
|
+
if g.adj[u]
|
25
|
+
g.adj[u].each do |v, options|
|
26
|
+
if !vertices_spanned.include?(v) && options[:weight] < min_cost
|
27
|
+
min_edge = [u, v]
|
28
|
+
min_cost = options[:weight]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
[min_edge, min_cost]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
# require_relative "graph.rb"
|
2
|
+
=begin
|
3
|
+
Class for directed graphs.
|
4
|
+
|
5
|
+
DiGraphs hold directed edges. Self loops are allowed but parallel edges are not.
|
6
|
+
|
7
|
+
See Also
|
8
|
+
--------
|
9
|
+
Graph
|
10
|
+
MultiGraph
|
11
|
+
=end
|
12
|
+
|
13
|
+
module Networkr
|
14
|
+
class DiGraph < Graph
|
15
|
+
|
16
|
+
attr_accessor :pred, :succ
|
17
|
+
|
18
|
+
def initialize(options={})
|
19
|
+
super(options)
|
20
|
+
@pred = {}
|
21
|
+
@succ = @adj
|
22
|
+
end
|
23
|
+
|
24
|
+
def add_node(node, options={})
|
25
|
+
if !@succ.include?(node)
|
26
|
+
@succ[node] = {}
|
27
|
+
@pred[node] = {}
|
28
|
+
@nodes[node] = options
|
29
|
+
else
|
30
|
+
@nodes[node].merge!(options)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def remove_node(node)
|
35
|
+
if @nodes[node]
|
36
|
+
@nodes.delete(node)
|
37
|
+
else
|
38
|
+
raise NetworkrError, "Node #{node} is not in graph"
|
39
|
+
end
|
40
|
+
|
41
|
+
nbrs = @succ[node]
|
42
|
+
nbrs.each_key do |u|
|
43
|
+
@pred[u].delete(node)
|
44
|
+
end
|
45
|
+
@succ.delete(node)
|
46
|
+
|
47
|
+
@pred[node].each_key do |u|
|
48
|
+
@succ[u].delete(node)
|
49
|
+
end
|
50
|
+
@pred.delete(node)
|
51
|
+
end
|
52
|
+
|
53
|
+
def add_edge(u, v, options={})
|
54
|
+
if !@succ[u]
|
55
|
+
@succ[u] = {}
|
56
|
+
@pred[u] = {}
|
57
|
+
@nodes[u] = {}
|
58
|
+
end
|
59
|
+
if !@succ[v]
|
60
|
+
@succ[v] = {}
|
61
|
+
@pred[v] = {}
|
62
|
+
@nodes[v] = {}
|
63
|
+
end
|
64
|
+
data = @adj[u][v] || {}
|
65
|
+
data.merge!(options)
|
66
|
+
@succ[u][v]=data
|
67
|
+
@pred[v][u]=data
|
68
|
+
end
|
69
|
+
|
70
|
+
def remove_edge(u, v)
|
71
|
+
if @succ[u] && @succ[u][v]
|
72
|
+
@succ[u].delete(v)
|
73
|
+
@pred[v].delete(u)
|
74
|
+
else
|
75
|
+
raise NetworkrError, "Edge #{u}->#{v} not in graph"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def has_successor?(u, v)
|
80
|
+
@succ[u] && @succ[u][v]
|
81
|
+
end
|
82
|
+
|
83
|
+
def has_predecessor?(u, v)
|
84
|
+
@pred[u] && @pred[u][v]
|
85
|
+
end
|
86
|
+
|
87
|
+
def clear
|
88
|
+
@succ.clear
|
89
|
+
@pred.clear
|
90
|
+
@nodes.clear
|
91
|
+
@graph.clear
|
92
|
+
end
|
93
|
+
|
94
|
+
def is_directed?
|
95
|
+
true
|
96
|
+
end
|
97
|
+
|
98
|
+
def to_s
|
99
|
+
res = ''
|
100
|
+
@adj.each do |u, nbrs|
|
101
|
+
nbrs.each do |v, options|
|
102
|
+
res = "#{res}#{u}->#{v} #{options}\n"
|
103
|
+
end
|
104
|
+
end
|
105
|
+
res[0...-1]
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,183 @@
|
|
1
|
+
# Class for undirected graphs.
|
2
|
+
#
|
3
|
+
# A Graph stores nodes and edges with optional attributes.
|
4
|
+
#
|
5
|
+
# Graphs hold undirected edges. Self loops are allowed but parallel edges are not.
|
6
|
+
#
|
7
|
+
# Nodes can be arbitrary hashable Ruby objects with optional attributes.
|
8
|
+
#
|
9
|
+
# Edges are represented as links between nodes with option attributes.
|
10
|
+
#
|
11
|
+
# Parameters
|
12
|
+
# ----------
|
13
|
+
# options: attributes to add to graph as key/value pairs, optional (default is {})
|
14
|
+
#
|
15
|
+
# See Also
|
16
|
+
# --------
|
17
|
+
# DiGraph
|
18
|
+
#
|
19
|
+
# Examples
|
20
|
+
# --------
|
21
|
+
# Create an empty graph with no nodes and edges.
|
22
|
+
#
|
23
|
+
# >>> g = Graph()
|
24
|
+
#
|
25
|
+
# g can be grown in several ways
|
26
|
+
#
|
27
|
+
# **Nodes:**
|
28
|
+
#
|
29
|
+
# Add one node at a time:
|
30
|
+
#
|
31
|
+
# >>> g.add_node(1)
|
32
|
+
#
|
33
|
+
# **Edges:**
|
34
|
+
#
|
35
|
+
# Add one edge at a time:
|
36
|
+
#
|
37
|
+
# >>> g.add_edge(1, 2)
|
38
|
+
#
|
39
|
+
# If some edges connect nodes not yet in the graph, the nodes are added automatically. There are no erros when adding nodes or edges that already exist.
|
40
|
+
#
|
41
|
+
# **Attributes:**
|
42
|
+
#
|
43
|
+
# Each graph, node, and edge can hold key/value attribute pairs in an associated attribute hash. By default these are empty, but can be added or changed using add_node, add_node, or direct manipulation of the attribute hashes named graph, nodes, and adj.
|
44
|
+
#
|
45
|
+
# >>> g = Graph(name: "users")
|
46
|
+
# >>> g.graph
|
47
|
+
# { name: "users" }
|
48
|
+
#
|
49
|
+
# Add/update node attributes using add_node or g.nodes:
|
50
|
+
#
|
51
|
+
# >>> g.add_node(1, username: "janedoe")
|
52
|
+
# >>> g.add_node(3, username: "johndoe")
|
53
|
+
# >>> g.nodes[1]
|
54
|
+
# { username: "janedoe" }
|
55
|
+
# >>> g.nodes[1][:score] = 10
|
56
|
+
# >>> g.nodes[1].delete(:score) # remove attribute
|
57
|
+
# >>> g.nodes
|
58
|
+
# { 1: { username: "janedoe" }, 3: { username: "johndoe" } }
|
59
|
+
#
|
60
|
+
# Add/update edge attributes using add_edge or g.adj:
|
61
|
+
#
|
62
|
+
# >>> g.add_edge(1, 2, weight: 10)
|
63
|
+
# >>> g.adj[1][2][:weight] = 4
|
64
|
+
# >>> g.adj[1][2]
|
65
|
+
# { weight: 4 }
|
66
|
+
#
|
67
|
+
# **Representation:**
|
68
|
+
# :TODO
|
69
|
+
# g.to_s
|
70
|
+
#
|
71
|
+
# 1-2 { weight: 4 }
|
72
|
+
# 2-6 { weight: 10 }
|
73
|
+
# 4-6 { weight: 8 }
|
74
|
+
# 3-5 { weight: 5 }
|
75
|
+
# 5-6 { weight: 1 }
|
76
|
+
|
77
|
+
class NetworkrError < RuntimeError
|
78
|
+
end
|
79
|
+
|
80
|
+
module Networkr
|
81
|
+
class Graph
|
82
|
+
attr_accessor :graph, :nodes, :adj
|
83
|
+
|
84
|
+
def initialize(options = {})
|
85
|
+
@graph = options
|
86
|
+
@nodes = {}
|
87
|
+
@adj = {}
|
88
|
+
end
|
89
|
+
|
90
|
+
def add_node(node, options = {})
|
91
|
+
if @nodes[node]
|
92
|
+
@nodes[node].merge!(options)
|
93
|
+
else
|
94
|
+
@nodes[node] = options
|
95
|
+
@adj[node] = {}
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def remove_node(node)
|
100
|
+
if @nodes[node]
|
101
|
+
@nodes.delete(node)
|
102
|
+
else
|
103
|
+
raise NetworkrError, "Node #{node} not in graph"
|
104
|
+
end
|
105
|
+
|
106
|
+
nbrs = @adj[node]
|
107
|
+
nbrs.each_key do |nbr|
|
108
|
+
@adj[nbr].delete(node)
|
109
|
+
end
|
110
|
+
@adj.delete(node)
|
111
|
+
end
|
112
|
+
|
113
|
+
def add_edge(u, v, options = {})
|
114
|
+
if !@nodes[u]
|
115
|
+
@adj[u] = {}
|
116
|
+
@nodes[u] = {}
|
117
|
+
end
|
118
|
+
if !@nodes[v]
|
119
|
+
@adj[v] = {}
|
120
|
+
@nodes[v] = {}
|
121
|
+
end
|
122
|
+
data = @adj[u][v] || {}
|
123
|
+
data.merge!(options)
|
124
|
+
@adj[u][v] = data
|
125
|
+
@adj[v][u] = data
|
126
|
+
end
|
127
|
+
|
128
|
+
def remove_edge(u, v)
|
129
|
+
if @adj[u][v]
|
130
|
+
@adj[u].delete(v)
|
131
|
+
if u != v
|
132
|
+
@adj[v].delete(u)
|
133
|
+
end
|
134
|
+
else
|
135
|
+
raise NetworkrError, "Edge #{u}-#{v} not in graph"
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def children_of(node)
|
140
|
+
@adj[node]
|
141
|
+
end
|
142
|
+
|
143
|
+
def has_node?(node)
|
144
|
+
@nodes.include?(node)
|
145
|
+
end
|
146
|
+
|
147
|
+
def has_edge?(u, v)
|
148
|
+
@adj[u].include?(v)
|
149
|
+
end
|
150
|
+
|
151
|
+
def length
|
152
|
+
@nodes.length
|
153
|
+
end
|
154
|
+
|
155
|
+
def clear
|
156
|
+
@graph.clear
|
157
|
+
@nodes.clear
|
158
|
+
@adj.clear
|
159
|
+
end
|
160
|
+
|
161
|
+
def deep_copy
|
162
|
+
Marshal.load(Marshal.dump(self))
|
163
|
+
end
|
164
|
+
|
165
|
+
def is_multigraph?
|
166
|
+
false
|
167
|
+
end
|
168
|
+
|
169
|
+
def is_directed?
|
170
|
+
false
|
171
|
+
end
|
172
|
+
|
173
|
+
def to_s
|
174
|
+
res = ''
|
175
|
+
@adj.each do |u, nbrs|
|
176
|
+
nbrs.each do |v, options|
|
177
|
+
res = "#{res}#{u}-#{v} #{options}\n"
|
178
|
+
end
|
179
|
+
end
|
180
|
+
res[0...-1]
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
# require_relative "graph.rb"
|
2
|
+
=begin
|
3
|
+
Class for undirected graphs that can store parallel edges.
|
4
|
+
|
5
|
+
A MultiGraph holds undirected edges. Self loops are allowed.
|
6
|
+
|
7
|
+
See Also
|
8
|
+
--------
|
9
|
+
Graph
|
10
|
+
DiGraph
|
11
|
+
=end
|
12
|
+
|
13
|
+
module Networkr
|
14
|
+
class MultiGraph < Graph
|
15
|
+
def new_edge_key(u, v)
|
16
|
+
=begin
|
17
|
+
return an unused key for edges between nodes 'u' and 'v'.
|
18
|
+
|
19
|
+
The nodes 'u' and 'v' do not need to be already in the graph.
|
20
|
+
|
21
|
+
Notes
|
22
|
+
-----
|
23
|
+
|
24
|
+
The new key is the number of existing edges between 'u' and 'v', increased if necessary to ensure uniqueness. The first edge will have key 0, the second edge 1, etc. If an edge is removed, new keys may not be in this order.
|
25
|
+
|
26
|
+
Parameters
|
27
|
+
----------
|
28
|
+
u, v: nodes
|
29
|
+
|
30
|
+
Returns
|
31
|
+
-------
|
32
|
+
key: int
|
33
|
+
=end
|
34
|
+
if @adj[u] && @adj[u][v]
|
35
|
+
keys = @adj[u][v]
|
36
|
+
key = keys.length
|
37
|
+
while keys.include?(key)
|
38
|
+
key += 1
|
39
|
+
end
|
40
|
+
return key
|
41
|
+
else
|
42
|
+
return 0
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def add_edge(u, v, key=nil, options={})
|
47
|
+
if !@adj.include?(u)
|
48
|
+
@adj[u] = {}
|
49
|
+
@nodes[u] = {}
|
50
|
+
end
|
51
|
+
if !@adj.include?(v)
|
52
|
+
@adj[v] = {}
|
53
|
+
@nodes[v] = {}
|
54
|
+
end
|
55
|
+
if !key
|
56
|
+
key = new_edge_key(u, v)
|
57
|
+
end
|
58
|
+
if @adj[u].include?(v)
|
59
|
+
keys = @adj[u][v]
|
60
|
+
data = keys[key] || {}
|
61
|
+
data.merge!(options)
|
62
|
+
keys[key] = data
|
63
|
+
else
|
64
|
+
data = options
|
65
|
+
keys = {}
|
66
|
+
keys[key] = data
|
67
|
+
@adj[u][v] = keys
|
68
|
+
@adj[v][u] = keys
|
69
|
+
end
|
70
|
+
key
|
71
|
+
end
|
72
|
+
|
73
|
+
def remove_edge(u, v, key=nil)
|
74
|
+
keys = @adj[u][v]
|
75
|
+
if keys
|
76
|
+
if !key
|
77
|
+
keys.shift
|
78
|
+
else
|
79
|
+
if keys[key]
|
80
|
+
keys.delete(key)
|
81
|
+
else
|
82
|
+
raise NetworkrError, "Edge #{u}-#{v} with key #{key} not in graph"
|
83
|
+
end
|
84
|
+
end
|
85
|
+
if keys.length == 0
|
86
|
+
@adj[v].delete(v)
|
87
|
+
if u != v
|
88
|
+
@adj[v].delete(u)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
else
|
92
|
+
raise NetworkrError, "Edge #{u}-#{v} not in graph"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def is_multigraph?
|
97
|
+
true
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
data/lib/utils/heap.rb
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
class BinaryMinHeap
|
2
|
+
def initialize(&prc)
|
3
|
+
@store = []
|
4
|
+
@index_map {}
|
5
|
+
@prc = prc || Proc.new { |a, b| a <=> b }
|
6
|
+
end
|
7
|
+
|
8
|
+
def count
|
9
|
+
@store.length
|
10
|
+
end
|
11
|
+
|
12
|
+
def pop
|
13
|
+
return nil if count == 0
|
14
|
+
|
15
|
+
self.class.swap!(@store, @index_map, 0, count - 1)
|
16
|
+
|
17
|
+
val = @store.pop
|
18
|
+
@index_map.delete(val)
|
19
|
+
|
20
|
+
unless count == 0
|
21
|
+
self.class.heapify_down(@store, @index_map, 0, &prc)
|
22
|
+
end
|
23
|
+
|
24
|
+
val
|
25
|
+
end
|
26
|
+
|
27
|
+
def peek
|
28
|
+
return nil if count == 0
|
29
|
+
@store.first
|
30
|
+
end
|
31
|
+
|
32
|
+
def push(val)
|
33
|
+
@store << val
|
34
|
+
@index_map[val] = count - 1
|
35
|
+
self.class.heapify_up(@store, @index_map, count - 1, &prc)
|
36
|
+
end
|
37
|
+
|
38
|
+
def reduce!(val)
|
39
|
+
index = @index_map[val]
|
40
|
+
self.class.heapify_up(@store, @index_map, index, &prc)
|
41
|
+
end
|
42
|
+
|
43
|
+
protected
|
44
|
+
attr_accessor :prc, :store, :index_map
|
45
|
+
|
46
|
+
public
|
47
|
+
def self.swap!(array, index_map, i1, i2)
|
48
|
+
array[i1], array[i2] = array[i2], array[i1]
|
49
|
+
index_map[parent_val], index_map[child_val] = child_idx, parent_idx
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.child_indices(len, parent_index)
|
53
|
+
[2 * parent_index + 1, 2 * parent_index + 2].select { |idx| idx < len }
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.parent_index(child_index)
|
57
|
+
if child_index == 0
|
58
|
+
return nil
|
59
|
+
else
|
60
|
+
(child_index - 1) / 2
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.heapify_down(array, index_map, parent_idx, len = array.length, &prc)
|
65
|
+
prc ||= Proc.new { |a, b| a <=> b }
|
66
|
+
|
67
|
+
while parent_idx <= parent_index(len - 1)
|
68
|
+
children = child_indices(len, parent_idx)
|
69
|
+
|
70
|
+
if children.length == 1
|
71
|
+
smallest_child_idx = children[0]
|
72
|
+
else
|
73
|
+
child1_idx, child2_idx = children[0], children[1]
|
74
|
+
|
75
|
+
if prc.call(array[child1_idx], array[child2_idx]) <= 0
|
76
|
+
smallest_child_idx = child1_idx
|
77
|
+
else
|
78
|
+
smallest_child_idx = child2_idx
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
if prc.call(array[parent_idx], array[smallest_child_idx]) > 0
|
83
|
+
swap!(array, index_map, parent_idx, smallest_child_idx)
|
84
|
+
parent_idx = smallest_child_idx
|
85
|
+
else
|
86
|
+
break
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
|
91
|
+
array
|
92
|
+
end
|
93
|
+
|
94
|
+
def self.heapify_up(array, child_idx, len = array.length, &prc)
|
95
|
+
prc ||= Proc.new { |a, b| a <=> b }
|
96
|
+
|
97
|
+
while child_idx > 0
|
98
|
+
parent_idx = parent_index(child_idx)
|
99
|
+
|
100
|
+
if prc.call(array[parent_idx], array[child_idx]) > 0
|
101
|
+
swap!(array, index_map, parent_idx, child_idx)
|
102
|
+
child_idx = parent_idx
|
103
|
+
else
|
104
|
+
break
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
108
|
+
|
109
|
+
array
|
110
|
+
end
|
111
|
+
end
|
data/networkr.gemspec
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'networkr/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "networkr"
|
8
|
+
spec.version = Networkr::VERSION
|
9
|
+
spec.authors = ["Yi-Ke Peng"]
|
10
|
+
spec.email = ["yi.ke.peng@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{NetworkR}
|
13
|
+
spec.description = %q{A gem for creating and manipulating graphs in Ruby.}
|
14
|
+
spec.homepage = "http://github.com/ykpeng/networkr"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
|
18
|
+
# to allow pushing to a single host or delete this section to allow pushing to any host.
|
19
|
+
# if spec.respond_to?(:metadata)
|
20
|
+
# spec.metadata['allowed_push_host'] = "TODO: Set to 'http://mygemserver.com'"
|
21
|
+
# else
|
22
|
+
# raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
|
23
|
+
# end
|
24
|
+
|
25
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
26
|
+
spec.bindir = "exe"
|
27
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
28
|
+
spec.require_paths = ["lib"]
|
29
|
+
|
30
|
+
spec.add_development_dependency "bundler", "~> 1.12"
|
31
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
32
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
33
|
+
end
|
metadata
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: networkr
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Yi-Ke Peng
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-07-24 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.12'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.12'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.0'
|
55
|
+
description: A gem for creating and manipulating graphs in Ruby.
|
56
|
+
email:
|
57
|
+
- yi.ke.peng@gmail.com
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- ".rspec"
|
63
|
+
- Gemfile
|
64
|
+
- Gemfile.lock
|
65
|
+
- LICENSE.txt
|
66
|
+
- README.md
|
67
|
+
- Rakefile
|
68
|
+
- bin/console
|
69
|
+
- bin/setup
|
70
|
+
- lib/networkr.rb
|
71
|
+
- lib/networkr/algorithms/dijkstra.rb
|
72
|
+
- lib/networkr/algorithms/karger.rb
|
73
|
+
- lib/networkr/algorithms/kosaraju.rb
|
74
|
+
- lib/networkr/algorithms/prim.rb
|
75
|
+
- lib/networkr/graphs/digraph.rb
|
76
|
+
- lib/networkr/graphs/graph.rb
|
77
|
+
- lib/networkr/graphs/multigraph.rb
|
78
|
+
- lib/networkr/version.rb
|
79
|
+
- lib/utils/heap.rb
|
80
|
+
- networkr.gemspec
|
81
|
+
homepage: http://github.com/ykpeng/networkr
|
82
|
+
licenses:
|
83
|
+
- MIT
|
84
|
+
metadata: {}
|
85
|
+
post_install_message:
|
86
|
+
rdoc_options: []
|
87
|
+
require_paths:
|
88
|
+
- lib
|
89
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
90
|
+
requirements:
|
91
|
+
- - ">="
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
95
|
+
requirements:
|
96
|
+
- - ">="
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: '0'
|
99
|
+
requirements: []
|
100
|
+
rubyforge_project:
|
101
|
+
rubygems_version: 2.2.2
|
102
|
+
signing_key:
|
103
|
+
specification_version: 4
|
104
|
+
summary: NetworkR
|
105
|
+
test_files: []
|