networkx 0.1.0 → 0.2.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 +5 -5
- data/{CODE_OF_CONDUCT.md → .github/CODE_OF_CONDUCT.md} +0 -0
- data/{CONTRIBUTING.md → .github/CONTRIBUTING.md} +21 -11
- data/.github/ISSUE_TEMPLATE.md +15 -0
- data/.github/PULL_REQUEST_TEMPLATE.md +10 -0
- data/.github/workflows/ci.yml +17 -0
- data/.github/workflows/doc.yml +23 -0
- data/.github/workflows/gem-push.yml +45 -0
- data/.rspec +0 -1
- data/.rubocop.yml +56 -63
- data/.yardopts +0 -1
- data/README.md +27 -27
- data/Rakefile +2 -3
- data/lib/networkx/auxillary_functions/cliques.rb +62 -0
- data/lib/networkx/auxillary_functions/cycles.rb +114 -0
- data/lib/networkx/auxillary_functions/dag.rb +59 -0
- data/lib/networkx/auxillary_functions/eccentricity.rb +37 -0
- data/lib/networkx/auxillary_functions/mis.rb +23 -0
- data/lib/networkx/auxillary_functions/mst.rb +33 -0
- data/lib/networkx/auxillary_functions/union_find.rb +104 -0
- data/lib/networkx/auxillary_functions/vitality.rb +13 -0
- data/lib/networkx/auxillary_functions/wiener.rb +13 -0
- data/lib/networkx/converters/to_csv.rb +45 -0
- data/lib/networkx/converters/to_json.rb +37 -0
- data/lib/networkx/digraph.rb +234 -0
- data/lib/networkx/flow/capacityscaling.rb +249 -0
- data/lib/networkx/flow/edmondskarp.rb +115 -0
- data/lib/networkx/flow/preflowpush.rb +249 -0
- data/lib/networkx/flow/shortestaugmentingpath.rb +154 -0
- data/lib/networkx/flow/utils.rb +139 -0
- data/lib/networkx/graph.rb +448 -0
- data/lib/networkx/link_analysis/hits.rb +59 -0
- data/lib/networkx/link_analysis/pagerank.rb +89 -0
- data/lib/networkx/multidigraph.rb +249 -0
- data/lib/networkx/multigraph.rb +199 -0
- data/lib/networkx/operators/all.rb +65 -0
- data/lib/networkx/operators/binary.rb +222 -0
- data/lib/networkx/operators/product.rb +201 -0
- data/lib/networkx/operators/unary.rb +17 -0
- data/lib/networkx/others/bridges.rb +30 -0
- data/lib/networkx/others/generators.rb +237 -0
- data/lib/networkx/others/grid_2d_graph.rb +38 -0
- data/lib/networkx/others/info.rb +11 -0
- data/lib/networkx/others/number_connected_components.rb +17 -0
- data/lib/networkx/others/reads.rb +52 -0
- data/lib/networkx/shortest_path/astar.rb +73 -0
- data/lib/networkx/shortest_path/dense.rb +29 -0
- data/lib/networkx/shortest_path/unweighted.rb +136 -0
- data/lib/networkx/shortest_path/weighted.rb +417 -0
- data/lib/networkx/to_matrix.rb +51 -0
- data/lib/networkx/traversals/bfs.rb +110 -0
- data/lib/networkx/traversals/dfs.rb +135 -0
- data/lib/networkx/traversals/edge_dfs.rb +114 -0
- data/lib/networkx/version.rb +1 -1
- data/lib/networkx.rb +43 -1
- data/networkx.gemspec +14 -12
- metadata +118 -62
- data/.rspec_formatter.rb +0 -24
- data/.travis.yml +0 -18
- data/Guardfile +0 -7
@@ -0,0 +1,59 @@
|
|
1
|
+
module NetworkX
|
2
|
+
# Returns the descendants of a given node
|
3
|
+
#
|
4
|
+
# @param graph [Graph, DiGraph, MultiGraph, MultiDiGraph] a graph
|
5
|
+
# @param source [Object] node to find descendents of
|
6
|
+
#
|
7
|
+
# @return [Array<Object>] Array of the descendants
|
8
|
+
def self.descendants(graph, source)
|
9
|
+
raise ArgumentError, 'Source is not present in the graph!' unless graph.node?(source)
|
10
|
+
|
11
|
+
des = single_source_shortest_path_length(graph, source).map { |u, _| u }.uniq
|
12
|
+
des - [source]
|
13
|
+
end
|
14
|
+
|
15
|
+
# Returns the ancestors of a given node
|
16
|
+
#
|
17
|
+
# @param graph [Graph, DiGraph, MultiGraph, MultiDiGraph] a graph
|
18
|
+
# @param source [Object] node to find ancestors of
|
19
|
+
#
|
20
|
+
# @return [Array<Object>] Array of the ancestors
|
21
|
+
def self.ancestors(graph, source)
|
22
|
+
raise ArgumentError, 'Source is not present in the graph!' unless graph.node?(source)
|
23
|
+
|
24
|
+
anc = single_source_shortest_path_length(graph.reverse, source).map { |u, _| u }.uniq
|
25
|
+
anc - [source]
|
26
|
+
end
|
27
|
+
|
28
|
+
# Returns the nodes arranged in the topologically sorted fashion
|
29
|
+
#
|
30
|
+
# @param graph [DiGraph] a graph
|
31
|
+
#
|
32
|
+
# @return [Array<Object>] Array of the nodes
|
33
|
+
def self.topological_sort(graph)
|
34
|
+
raise ArgumentError, 'Topological Sort not defined on undirected graphs!' unless graph.directed?
|
35
|
+
|
36
|
+
nodes = []
|
37
|
+
indegree_map = graph.nodes.each_key.map do |u|
|
38
|
+
[u, graph.in_degree(u)] if graph.in_degree(u).positive?
|
39
|
+
end.compact.to_h
|
40
|
+
zero_indegree = graph.nodes.each_key.select { |u| graph.in_degree(u).zero? }
|
41
|
+
|
42
|
+
until zero_indegree.empty?
|
43
|
+
node = zero_indegree.shift
|
44
|
+
raise ArgumentError, 'Graph changed during iteration!' unless graph.nodes.has_key?(node)
|
45
|
+
|
46
|
+
graph.adj[node].each_key do |child|
|
47
|
+
indegree_map[child] -= 1
|
48
|
+
if indegree_map[child].zero?
|
49
|
+
zero_indegree << child
|
50
|
+
indegree_map.delete(child)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
nodes << node
|
54
|
+
end
|
55
|
+
raise ArgumentError, 'Graph contains cycle or graph changed during iteration!' unless indegree_map.empty?
|
56
|
+
|
57
|
+
nodes
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module NetworkX
|
2
|
+
# Returns the eccentricity of a particular node or all nodes
|
3
|
+
#
|
4
|
+
# @param graph [Graph, DiGraph, MultiGraph, MultiDiGraph] a graph
|
5
|
+
# @param node [Object] node to find the eccentricity of
|
6
|
+
#
|
7
|
+
# @return [Array<Numeric>, Numeric] eccentricity/eccentricites of all nodes
|
8
|
+
def self.eccentricity(graph, node = nil)
|
9
|
+
e = {}
|
10
|
+
graph.nodes.each do |u, _|
|
11
|
+
length = single_source_shortest_path_length(graph, u)
|
12
|
+
l = length.length
|
13
|
+
raise ArgumentError, 'Found infinite path length!' unless l == graph.nodes.length
|
14
|
+
|
15
|
+
e[u] = length.max_by { |a| a[1] }[1]
|
16
|
+
end
|
17
|
+
node.nil? ? e : e[node]
|
18
|
+
end
|
19
|
+
|
20
|
+
# Returns the diameter of a graph
|
21
|
+
#
|
22
|
+
# @param graph [Graph, DiGraph, MultiGraph, MultiDiGraph] a graph
|
23
|
+
#
|
24
|
+
# @return [Numeric] diameter of the graph
|
25
|
+
def self.diameter(graph)
|
26
|
+
eccentricity(graph).values.max
|
27
|
+
end
|
28
|
+
|
29
|
+
# Returns the radius of a graph
|
30
|
+
#
|
31
|
+
# @param graph [Graph, DiGraph, MultiGraph, MultiDiGraph] a graph
|
32
|
+
#
|
33
|
+
# @return [Numeric] radius of the graph
|
34
|
+
def self.radius(graph)
|
35
|
+
eccentricity(graph).values.min
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module NetworkX
|
2
|
+
# Returns the maximal independent set of a graph
|
3
|
+
#
|
4
|
+
# @param graph [Graph, DiGraph, MultiGraph, MultiDiGraph] a graph
|
5
|
+
# @param nodes [Object] nodes to be considered in the MIS
|
6
|
+
#
|
7
|
+
# @return [Numeric] radius of the graph
|
8
|
+
def self.maximal_independent_set(graph, nodes)
|
9
|
+
raise 'The array containing the nodes should be a subset of the graph!' if (graph.nodes.keys - nodes).empty?
|
10
|
+
|
11
|
+
neighbours = []
|
12
|
+
nodes.each { |u| graph.adj[u].each { |v, _| neighbours |= [v] } }
|
13
|
+
raise 'Nodes is not an independent set of graph!' if (neighbours - nodes).empty?
|
14
|
+
|
15
|
+
available_nodes = graph.nodes.keys - (neighbours | nodes)
|
16
|
+
until available_nodes.empty?
|
17
|
+
node = available_nodes.sample
|
18
|
+
nodes << node
|
19
|
+
available_nodes -= (graph.adj[node].keys + [node])
|
20
|
+
end
|
21
|
+
nodes
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module NetworkX
|
2
|
+
# Helper function for the minimum spanning tree
|
3
|
+
#
|
4
|
+
def self.get_edges_weights(graph)
|
5
|
+
edges = []
|
6
|
+
graph.adj.each do |u, u_edges|
|
7
|
+
u_edges.each do |v, uv_attrs|
|
8
|
+
edges << [[u, v], uv_attrs[:weight] || Float::INFINITY]
|
9
|
+
end
|
10
|
+
end
|
11
|
+
edges
|
12
|
+
end
|
13
|
+
|
14
|
+
# Returns the minimum spanning tree of a graph
|
15
|
+
#
|
16
|
+
# @param graph [Graph, DiGraph] a graph
|
17
|
+
#
|
18
|
+
# @return [DiGraph, Graph] a minimum spanning tree of the graph
|
19
|
+
def self.minimum_spanning_tree(graph)
|
20
|
+
mst = Marshal.load(Marshal.dump(graph))
|
21
|
+
mst.clear
|
22
|
+
edges = get_edges_weights(graph).sort_by { |a| a[1] }
|
23
|
+
union_find = UnionFind.new(graph.nodes.keys)
|
24
|
+
while edges.any? && mst.nodes.length <= graph.nodes.length
|
25
|
+
edge = edges.shift
|
26
|
+
unless union_find.connected?(edge[0][0], edge[0][1])
|
27
|
+
union_find.union(edge[0][0], edge[0][1])
|
28
|
+
mst.add_edge(edge[0][0], edge[0][1], **graph.adj[edge[0][0]][edge[0][1]])
|
29
|
+
end
|
30
|
+
end
|
31
|
+
mst
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
module NetworkX
|
2
|
+
# Union Find Tree
|
3
|
+
#
|
4
|
+
# Reference
|
5
|
+
# - [ac-library-rb DSU (CC0)](https://github.com/universato/ac-library-rb/blob/main/lib/dsu.rb)
|
6
|
+
# - [Python NetworkX UnionFind](https://networkx.org/documentation/stable/_modules/networkx/utils/union_find.html)
|
7
|
+
#
|
8
|
+
#
|
9
|
+
# @attr_reader parents [Hash{ Object => Object }] Return parent of each element
|
10
|
+
# @attr_reader weights [Hash{ Object => Integer }] Return weight of each element
|
11
|
+
class UnionFind
|
12
|
+
attr_accessor :parents, :weights
|
13
|
+
|
14
|
+
# Constructor for initializing Union Find Tree
|
15
|
+
#
|
16
|
+
# @param nodes [?Array[Object]] nodes
|
17
|
+
#
|
18
|
+
# @return [UnionFind] Union Find Tree
|
19
|
+
def initialize(nodes = nil)
|
20
|
+
@weights = {}
|
21
|
+
@parents = {}
|
22
|
+
nodes&.each do |node|
|
23
|
+
@weights[node] = 1
|
24
|
+
@parents[node] = node
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Return the root of node
|
29
|
+
#
|
30
|
+
# @param node [Object] node
|
31
|
+
#
|
32
|
+
# @return [Object] root of node, leader of node
|
33
|
+
def [](node)
|
34
|
+
if @parents.has_key?(node)
|
35
|
+
@parents[node] == node ? node : (@parents[node] = self[@parents[node]])
|
36
|
+
else
|
37
|
+
@weights[node] = 1
|
38
|
+
@parents[node] = node
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Return the root of node
|
43
|
+
#
|
44
|
+
# @param node [Object] node
|
45
|
+
#
|
46
|
+
# @return [Object] root of node, leader of node
|
47
|
+
def root(node)
|
48
|
+
@parents.has_key?(node) or raise ArgumentError.new, "#{node} is not a node"
|
49
|
+
|
50
|
+
@parents[node] == node ? node : (@parents[node] = root(@parents[node]))
|
51
|
+
end
|
52
|
+
|
53
|
+
def each(&block)
|
54
|
+
@parents.each_key(&block)
|
55
|
+
end
|
56
|
+
|
57
|
+
def to_sets
|
58
|
+
each.group_by { |node| root(node) }.values
|
59
|
+
end
|
60
|
+
alias groups to_sets
|
61
|
+
|
62
|
+
# Is each root of two nodes the same?
|
63
|
+
#
|
64
|
+
# @param node1 [Object] node
|
65
|
+
# @param node2 [Object] node
|
66
|
+
#
|
67
|
+
# @return [bool] Is each root of node1 and nodes_2 the same?
|
68
|
+
def connected?(node1, node2)
|
69
|
+
root(node1) == root(node2)
|
70
|
+
end
|
71
|
+
alias same? connected?
|
72
|
+
|
73
|
+
# Unite nodes.
|
74
|
+
#
|
75
|
+
# @param nodes [Array[Object]] nodes
|
76
|
+
#
|
77
|
+
# @return [Object | nil] root of united nodes
|
78
|
+
def union(*nodes)
|
79
|
+
return merge(*nodes) if nodes.size == 2
|
80
|
+
|
81
|
+
roots = nodes.map { |node| self[node] }.uniq
|
82
|
+
return if roots.size == 1
|
83
|
+
|
84
|
+
roots.sort_by! { |root| @weights[root] }
|
85
|
+
root = roots[-1]
|
86
|
+
roots[0...-1].each do |r|
|
87
|
+
@weights[root] += @weights[r]
|
88
|
+
@parents[r] = root
|
89
|
+
end
|
90
|
+
root
|
91
|
+
end
|
92
|
+
alias unite union
|
93
|
+
|
94
|
+
def merge(node1, node2)
|
95
|
+
x = self[node1]
|
96
|
+
y = self[node2]
|
97
|
+
return if x == y
|
98
|
+
|
99
|
+
x, y = y, x if @weights[x] < @weights[y]
|
100
|
+
@weights[x] += @weights[y]
|
101
|
+
@parents[y] = x
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module NetworkX
|
2
|
+
# Returns the closeness vitality of a node
|
3
|
+
#
|
4
|
+
# @param graph [Graph, DiGraph] a graph
|
5
|
+
# @param node [Object] node to compute closeness vitality of
|
6
|
+
#
|
7
|
+
# @return [Numeric] closeness vitality of the given node
|
8
|
+
def self.closeness_vitality(graph, node)
|
9
|
+
before = wiener_index(graph)
|
10
|
+
after = wiener_index(graph.subgraph(graph.nodes.keys - [node]))
|
11
|
+
before - after
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module NetworkX
|
2
|
+
# Returns the wiener index of the graph
|
3
|
+
#
|
4
|
+
# @param graph [Graph, DiGraph] a graph
|
5
|
+
#
|
6
|
+
# @return [Numeric] wiener index of the graph
|
7
|
+
def self.wiener_index(graph)
|
8
|
+
total = all_pairs_shortest_path_length(graph)
|
9
|
+
wiener_ind = 0
|
10
|
+
total.to_h.each { |_, distances| distances.to_h.each { |_, val| wiener_ind += val } }
|
11
|
+
graph.directed? ? wiener_ind : wiener_ind / 2
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module NetworkX
|
2
|
+
# Saves the graph in a csv file
|
3
|
+
#
|
4
|
+
# @param graph [Graph, DiGraph, MultiGraph, MultiDiGraph] a graph
|
5
|
+
# @param filename [String] filename of the graph
|
6
|
+
def self.graph_to_csv(graph, filename = 'graph.csv')
|
7
|
+
CSV.open(filename, 'wb') do |csv|
|
8
|
+
csv << [graph.class.name]
|
9
|
+
csv << ['graph_values']
|
10
|
+
csv << graph.graph.keys
|
11
|
+
csv << graph.graph.values
|
12
|
+
csv << ['graph_nodes']
|
13
|
+
graph.nodes.each do |u, attrs|
|
14
|
+
node_attrs = [u]
|
15
|
+
attrs.each do |k, v|
|
16
|
+
node_attrs << k
|
17
|
+
node_attrs << v
|
18
|
+
end
|
19
|
+
csv << node_attrs
|
20
|
+
end
|
21
|
+
csv << ['graph_edges']
|
22
|
+
graph.adj.each do |u, u_edges|
|
23
|
+
u_edges.each do |v, uv_attrs|
|
24
|
+
if graph.multigraph?
|
25
|
+
uv_attrs.each do |key, attrs|
|
26
|
+
node_attrs = [u, v, key]
|
27
|
+
attrs.each do |k, k_attrs|
|
28
|
+
node_attrs << k
|
29
|
+
node_attrs << k_attrs
|
30
|
+
end
|
31
|
+
csv << node_attrs
|
32
|
+
end
|
33
|
+
else
|
34
|
+
node_attrs = [u, v]
|
35
|
+
uv_attrs.each do |k, vals|
|
36
|
+
node_attrs << k
|
37
|
+
node_attrs << vals
|
38
|
+
end
|
39
|
+
csv << node_attrs
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module NetworkX
|
2
|
+
# Returns a JSON object of the given graph
|
3
|
+
#
|
4
|
+
# @param graph [Graph, DiGraph, MultiGraph, MultiDiGraph] a graph
|
5
|
+
#
|
6
|
+
# @return [JSON] json encoded graph
|
7
|
+
def self.graph_to_json(graph)
|
8
|
+
json_hash = {}
|
9
|
+
json_hash[:class] = graph.class.name
|
10
|
+
json_hash[:graph] = graph.graph
|
11
|
+
json_hash[:nodes] = graph.nodes
|
12
|
+
json_hash[:adj] = graph.adj
|
13
|
+
json_hash.to_json
|
14
|
+
end
|
15
|
+
|
16
|
+
# Returns a graph from the json encoded graph
|
17
|
+
#
|
18
|
+
# @param json_str [JSON] json encoded string
|
19
|
+
#
|
20
|
+
# @return [Graph, DiGraph, MultiGraph, MultiDiGraph] a decoded graph
|
21
|
+
def self.json_to_graph(json_str)
|
22
|
+
graph_hash = JSON.parse(json_str)
|
23
|
+
case json_str['class']
|
24
|
+
when 'NetworkX::Graph'
|
25
|
+
graph = NetworkX::Graph.new(graph_hash.graph)
|
26
|
+
when 'NetworkX::MultiGraph'
|
27
|
+
graph = NetworkX::MultiGraph.new(graph_hash.graph)
|
28
|
+
when 'NetworkX::DiGraph'
|
29
|
+
graph = NetworkX::DiGraph.new(graph_hash.graph)
|
30
|
+
when 'NetworkX::MultiDiGraph'
|
31
|
+
graph = NetworkX::MultiDiGraph.new(graph_hash.graph)
|
32
|
+
end
|
33
|
+
graph.adj = graph_hash['adj']
|
34
|
+
graph.nodes = graph_hash['nodes']
|
35
|
+
graph
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,234 @@
|
|
1
|
+
module NetworkX
|
2
|
+
# Describes the class for making Directed Graphs
|
3
|
+
#
|
4
|
+
# @attr_reader adj [Hash{ Object => Hash{ Object => Hash{ Object => Object } } }]
|
5
|
+
# Stores the edges and their attributes in an adjencency list form
|
6
|
+
# @attr_reader pred [Hash{ Object => Hash{ Object => Hash{ Object => Object } } }]
|
7
|
+
# Stores the reverse edges and their attributes in an adjencency list form
|
8
|
+
# @attr_reader nodes [Hash{ Object => Hash{ Object => Object } }] Stores the nodes and their attributes
|
9
|
+
# @attr_reader graph [Hash{ Object => Object }] Stores the attributes of the graph
|
10
|
+
class DiGraph < Graph
|
11
|
+
attr_reader :adj, :graph, :pred
|
12
|
+
|
13
|
+
# Constructor for initializing graph
|
14
|
+
#
|
15
|
+
# @example Initialize a graph with attributes 'type' and 'name'
|
16
|
+
# graph = NetworkX::Graph.new(name: "Social Network", type: "undirected")
|
17
|
+
#
|
18
|
+
# @param graph_attrs [Hash{ Object => Object }] the graph attributes in a hash format
|
19
|
+
def initialize(**graph_attrs)
|
20
|
+
super(**graph_attrs)
|
21
|
+
|
22
|
+
@pred = {}
|
23
|
+
end
|
24
|
+
|
25
|
+
# Adds the respective edge
|
26
|
+
#
|
27
|
+
# @example Add an edge with attribute name
|
28
|
+
# graph.add_edge(node1, node2, name: "Edge1")
|
29
|
+
#
|
30
|
+
# @example Add an edge with no attribute
|
31
|
+
# graph.add_edge("Bangalore", "Chennai")
|
32
|
+
#
|
33
|
+
# @param node1 [Object] the first node of the edge
|
34
|
+
# @param node2 [Object] the second node of the edge
|
35
|
+
# @param edge_attrs [Hash{ Object => Object }] the hash of the edge attributes
|
36
|
+
def add_edge(node1, node2, **edge_attrs)
|
37
|
+
add_node(node1)
|
38
|
+
add_node(node2)
|
39
|
+
|
40
|
+
edge_attrs = (@adj[node1][node2] || {}).merge(edge_attrs)
|
41
|
+
@adj[node1][node2] = edge_attrs
|
42
|
+
@pred[node2][node1] = edge_attrs
|
43
|
+
end
|
44
|
+
|
45
|
+
# Adds a node and its attributes to the graph
|
46
|
+
#
|
47
|
+
# @example Add a node with attribute 'type'
|
48
|
+
# graph.add_node("Noida", type: "city")
|
49
|
+
#
|
50
|
+
# @param node [Object] the node object
|
51
|
+
# @param node_attrs [Hash{ Object => Object }] the hash of the attributes of the node
|
52
|
+
def add_node(node, **node_attrs)
|
53
|
+
super(node, **node_attrs)
|
54
|
+
|
55
|
+
@pred[node] = {} unless @pred.has_key?(node)
|
56
|
+
end
|
57
|
+
|
58
|
+
def nodes(data: true)
|
59
|
+
data ? @nodes : @nodes.keys
|
60
|
+
end
|
61
|
+
|
62
|
+
# Removes node from the graph
|
63
|
+
#
|
64
|
+
# @example
|
65
|
+
# graph.remove_node("Noida")
|
66
|
+
#
|
67
|
+
# @param node [Object] the node to be removed
|
68
|
+
def remove_node(node)
|
69
|
+
raise KeyError, "Error in deleting node #{node} from Graph." unless @nodes.has_key?(node)
|
70
|
+
|
71
|
+
neighbours = @adj[node]
|
72
|
+
neighbours.each_key { |k| @pred[k].delete(node) }
|
73
|
+
@pred[node].each_key do |k|
|
74
|
+
@adj[k].delete(node)
|
75
|
+
end
|
76
|
+
|
77
|
+
@pred.delete(node)
|
78
|
+
@adj.delete(node)
|
79
|
+
@nodes.delete(node)
|
80
|
+
end
|
81
|
+
|
82
|
+
# Removes edge from the graph
|
83
|
+
#
|
84
|
+
# @example
|
85
|
+
# graph.remove_edge('Noida', 'Bangalore')
|
86
|
+
#
|
87
|
+
# @param node1 [Object] the first node of the edge
|
88
|
+
# @param node2 [Object] the second node of the edge
|
89
|
+
def remove_edge(node1, node2)
|
90
|
+
raise KeyError, "#{node1} is not a valid node." unless @nodes.has_key?(node1)
|
91
|
+
raise KeyError, "#{node2} is not a valid node" unless @nodes.has_key?(node2)
|
92
|
+
raise KeyError, 'The given edge is not a valid one.' unless @adj[node1].has_key?(node2)
|
93
|
+
|
94
|
+
@adj[node1].delete(node2)
|
95
|
+
@pred[node2].delete(node1)
|
96
|
+
end
|
97
|
+
|
98
|
+
# Clears the graph
|
99
|
+
#
|
100
|
+
# @example
|
101
|
+
# graph.clear
|
102
|
+
def clear
|
103
|
+
super
|
104
|
+
|
105
|
+
@pred.clear
|
106
|
+
end
|
107
|
+
|
108
|
+
# Returns number of edges
|
109
|
+
#
|
110
|
+
# @example
|
111
|
+
# graph.number_of_edges
|
112
|
+
def number_of_edges
|
113
|
+
@adj.values.map(&:length).sum
|
114
|
+
end
|
115
|
+
|
116
|
+
# Returns the size of graph
|
117
|
+
#
|
118
|
+
# @example
|
119
|
+
# graph.size(true)
|
120
|
+
#
|
121
|
+
# @param is_weighted [Bool] if true, method returns sum of weights of all edges
|
122
|
+
# else returns number of edges
|
123
|
+
def size(is_weighted = false)
|
124
|
+
if is_weighted
|
125
|
+
graph_size = 0
|
126
|
+
@adj.each do |_, hash_val|
|
127
|
+
hash_val.each { |_, v| graph_size += v[:weight] if v.has_key?(:weight) }
|
128
|
+
end
|
129
|
+
return graph_size
|
130
|
+
end
|
131
|
+
number_of_edges
|
132
|
+
end
|
133
|
+
|
134
|
+
# Returns in-degree of a given node
|
135
|
+
#
|
136
|
+
# @example
|
137
|
+
# graph.in_degree(node)
|
138
|
+
#
|
139
|
+
# @param node [Object] the node whose in degree is to be calculated
|
140
|
+
def in_degree(node)
|
141
|
+
@pred[node].length
|
142
|
+
end
|
143
|
+
|
144
|
+
# Returns out-degree of a given node
|
145
|
+
#
|
146
|
+
# @example
|
147
|
+
# graph.out_degree(node)
|
148
|
+
#
|
149
|
+
# @param node [Object] the node whose out degree is to be calculated
|
150
|
+
def out_degree(node)
|
151
|
+
@adj[node].length
|
152
|
+
end
|
153
|
+
|
154
|
+
# Returns the reversed version of the graph
|
155
|
+
#
|
156
|
+
# @example
|
157
|
+
# graph.reverse
|
158
|
+
def reverse
|
159
|
+
new_graph = NetworkX::DiGraph.new(**@graph)
|
160
|
+
@nodes.each { |u, attrs| new_graph.add_node(u, **attrs) }
|
161
|
+
@adj.each do |u, edges|
|
162
|
+
edges.each { |v, attrs| new_graph.add_edge(v, u, **attrs) }
|
163
|
+
end
|
164
|
+
new_graph
|
165
|
+
end
|
166
|
+
|
167
|
+
# Returns the undirected version of the graph
|
168
|
+
#
|
169
|
+
# @example
|
170
|
+
# graph.to_undirected
|
171
|
+
def to_undirected
|
172
|
+
new_graph = NetworkX::Graph.new(**@graph)
|
173
|
+
@nodes.each { |u, attrs| new_graph.add_node(u, **attrs) }
|
174
|
+
@adj.each do |u, edges|
|
175
|
+
edges.each { |v, attrs| new_graph.add_edge(u, v, **attrs) }
|
176
|
+
end
|
177
|
+
new_graph
|
178
|
+
end
|
179
|
+
|
180
|
+
# Returns subgraph consisting of given array of nodes
|
181
|
+
#
|
182
|
+
# @example
|
183
|
+
# graph.subgraph(%w[Mumbai Nagpur])
|
184
|
+
#
|
185
|
+
# @param nodes [Array<Object>] the nodes to be included in the subgraph
|
186
|
+
def subgraph(nodes)
|
187
|
+
case nodes
|
188
|
+
when Array, Set
|
189
|
+
sub_graph = NetworkX::DiGraph.new(**@graph)
|
190
|
+
nodes.each do |u|
|
191
|
+
raise KeyError, "#{u} does not exist in the current graph!" unless node?(u)
|
192
|
+
|
193
|
+
sub_graph.add_node(u, **@nodes[u])
|
194
|
+
@adj[u].each do |v, uv_attrs|
|
195
|
+
sub_graph.add_edge(u, v, **uv_attrs) if @adj[u].has_key?(v) && nodes.include?(v)
|
196
|
+
end
|
197
|
+
end
|
198
|
+
sub_graph
|
199
|
+
else
|
200
|
+
raise ArgumentError, 'Expected Argument to be Array or Set of nodes, ' \
|
201
|
+
"received #{nodes.class.name} instead."
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
# Returns subgraph consisting of given edges
|
206
|
+
#
|
207
|
+
# @example
|
208
|
+
# graph.edge_subgraph([%w[Nagpur Wardha], %w[Nagpur Mumbai]])
|
209
|
+
#
|
210
|
+
# @param edges [Array<Object, Object>] the edges to be included in the subraph
|
211
|
+
def edge_subgraph(edges)
|
212
|
+
case edges
|
213
|
+
when Array, Set
|
214
|
+
sub_graph = NetworkX::DiGraph.new(**@graph)
|
215
|
+
edges.each do |u, v|
|
216
|
+
raise KeyError, "Edge between #{u} and #{v} does not exist in the graph!" unless @nodes.has_key?(u) \
|
217
|
+
&& @adj[u].has_key?(v)
|
218
|
+
|
219
|
+
sub_graph.add_node(u, **@nodes[u])
|
220
|
+
sub_graph.add_node(v, **@nodes[v])
|
221
|
+
sub_graph.add_edge(u, v, **@adj[u][v])
|
222
|
+
end
|
223
|
+
sub_graph
|
224
|
+
else
|
225
|
+
raise ArgumentError, 'Expected Argument to be Array or Set of edges, ' \
|
226
|
+
"received #{edges.class.name} instead."
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
def directed?
|
231
|
+
true
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|