bipartite_graph 0.0.2 → 0.0.3
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 +4 -4
- data/lib/bipartite_graph.rb +1 -0
- data/lib/bipartite_graph/complete_graph.rb +34 -0
- data/lib/bipartite_graph/edge_set.rb +3 -0
- data/lib/bipartite_graph/graph.rb +4 -0
- data/lib/bipartite_graph/hungarian_algorithm.rb +22 -12
- data/lib/bipartite_graph/version.rb +1 -1
- data/spec/acceptance/weighted_matching_spec.rb +27 -2
- metadata +2 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4ea037a79cf9dbd088c8d4351057ea39f62bdfa3
|
4
|
+
data.tar.gz: ab0f8cc6fb801adf2a3460b01e45a36e7679638e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0737da7745f9f99d8f2c7983d61d4b6277a840417afd5db021076733a024b811a345915fd1a6a9ac1a4ad1803a71a37bbb6b81f02f49a812e91609533977c7c6
|
7
|
+
data.tar.gz: 8a1b329e9270035a201e55cc036b29ed103e42073dcecea9c9f9d80e2e4aec7f8dbe356ca37e8389ed89f84d90e02c008f6d935cddcc3314fcb695700de978e6
|
data/lib/bipartite_graph.rb
CHANGED
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'delegate'
|
2
|
+
|
3
|
+
module BipartiteGraph
|
4
|
+
class CompleteGraph < SimpleDelegator
|
5
|
+
class FakeNode; end
|
6
|
+
|
7
|
+
def initialize(graph)
|
8
|
+
@original_graph = graph
|
9
|
+
@complete_graph = Graph.new
|
10
|
+
|
11
|
+
sinks = graph.sinks.dup
|
12
|
+
sink_count = sinks.length
|
13
|
+
sources = graph.sources.dup
|
14
|
+
source_count = sources.length
|
15
|
+
|
16
|
+
if sink_count > source_count
|
17
|
+
(sink_count - source_count).times { sources << FakeNode.new }
|
18
|
+
elsif source_count > sink_count
|
19
|
+
(source_count - sink_count).times { sinks << FakeNode.new }
|
20
|
+
end
|
21
|
+
|
22
|
+
sources.each do |source|
|
23
|
+
sinks.each do |sink|
|
24
|
+
edge = graph.edge_between(source, sink)
|
25
|
+
weight = edge ? edge.weight : 0
|
26
|
+
@complete_graph.add_edge(source, sink, weight)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
super(@complete_graph)
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
@@ -1,13 +1,32 @@
|
|
1
1
|
module BipartiteGraph
|
2
2
|
class HungarianAlgorithm
|
3
|
-
attr_reader :labelling, :matching, :graph
|
3
|
+
attr_reader :labelling, :matching, :graph, :original_graph
|
4
4
|
|
5
5
|
def initialize(graph)
|
6
|
-
@
|
7
|
-
@
|
6
|
+
@original_graph = graph
|
7
|
+
@graph = BipartiteGraph::CompleteGraph.new(graph)
|
8
|
+
@labelling = Labelling.new(@graph)
|
8
9
|
@matching = create_initial_matching
|
9
10
|
end
|
10
11
|
|
12
|
+
def solution
|
13
|
+
while matching.weight != labelling.total
|
14
|
+
root = (graph.sources - matching.sources).first
|
15
|
+
add_to_matching(root)
|
16
|
+
end
|
17
|
+
|
18
|
+
restricted_matching(matching, original_graph)
|
19
|
+
end
|
20
|
+
|
21
|
+
def restricted_matching(matching, graph)
|
22
|
+
m = Matching.new(graph)
|
23
|
+
|
24
|
+
matching.edges.each do |edge|
|
25
|
+
m.add_edge(edge) if edge.weight > 0 # will exclude fake nodes as they're
|
26
|
+
# only reachable from 0 edges
|
27
|
+
end
|
28
|
+
m
|
29
|
+
end
|
11
30
|
|
12
31
|
def create_initial_matching
|
13
32
|
eq_graph = labelling.equality_graph
|
@@ -117,15 +136,6 @@ module BipartiteGraph
|
|
117
136
|
end
|
118
137
|
end
|
119
138
|
|
120
|
-
def solution
|
121
|
-
while matching.weight != labelling.total
|
122
|
-
root = (graph.sources - matching.sources).first
|
123
|
-
add_to_matching(root)
|
124
|
-
end
|
125
|
-
|
126
|
-
matching
|
127
|
-
end
|
128
|
-
|
129
139
|
def equality_graph
|
130
140
|
labelling.equality_graph
|
131
141
|
end
|
@@ -25,7 +25,6 @@ describe "weighted matchings" do
|
|
25
25
|
end
|
26
26
|
|
27
27
|
describe "simple example 2" do
|
28
|
-
|
29
28
|
before do
|
30
29
|
[
|
31
30
|
['x1', 'y1', 3],
|
@@ -46,7 +45,7 @@ describe "weighted matchings" do
|
|
46
45
|
matching = graph.max_weight_matching.edges.map {|e| [e.from, e.to] }
|
47
46
|
|
48
47
|
expect(matching).to match_array([
|
49
|
-
['
|
48
|
+
['x2', 'y1'], ['x3', 'y2'], ['x4', 'y3'] # ['x1', 'y4'] with weight 0
|
50
49
|
])
|
51
50
|
end
|
52
51
|
end
|
@@ -68,4 +67,30 @@ describe "weighted matchings" do
|
|
68
67
|
])
|
69
68
|
end
|
70
69
|
end
|
70
|
+
|
71
|
+
describe "not connected-enough example" do
|
72
|
+
# the algorithm will run on some non-completely-connected graphs, but
|
73
|
+
# won't complete on all of them
|
74
|
+
# this example is constructed from simple example 2, so that the algorithm
|
75
|
+
# will fail (unless extra edges are added)
|
76
|
+
before do
|
77
|
+
[
|
78
|
+
['x1', 'y1', 3],
|
79
|
+
['x2', 'y1', 11],
|
80
|
+
['x3', 'y1', 10],
|
81
|
+
['x3', 'y3', 8],
|
82
|
+
['x3', 'y4', 7],
|
83
|
+
['x4', 'y3', 6],
|
84
|
+
['x4', 'y4', 4]
|
85
|
+
].each { |from, to, weight| graph.add_edge(from, to, weight) }
|
86
|
+
end
|
87
|
+
|
88
|
+
it "finds the solution" do
|
89
|
+
matching = graph.max_weight_matching.edges.map {|e| [e.from, e.to] }
|
90
|
+
|
91
|
+
expect(matching).to match_array([
|
92
|
+
['x2', 'y1'], ['x3', 'y4'], ['x4', 'y3']
|
93
|
+
])
|
94
|
+
end
|
95
|
+
end
|
71
96
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bipartite_graph
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tom Close
|
@@ -74,6 +74,7 @@ files:
|
|
74
74
|
- bipartite_graph.gemspec
|
75
75
|
- lib/bipartite_graph.rb
|
76
76
|
- lib/bipartite_graph/alternating_tree.rb
|
77
|
+
- lib/bipartite_graph/complete_graph.rb
|
77
78
|
- lib/bipartite_graph/edge.rb
|
78
79
|
- lib/bipartite_graph/edge_set.rb
|
79
80
|
- lib/bipartite_graph/graph.rb
|