blib/lib/Graph/Undirected/Hamiltonicity/Transforms.pm | |||
---|---|---|---|
Criterion | Covered | Total | % |
statement | 151 | 152 | 99.3 |
branch | 44 | 46 | 95.6 |
condition | 4 | 6 | 66.6 |
subroutine | 13 | 13 | 100.0 |
pod | 7 | 8 | 87.5 |
total | 219 | 225 | 97.3 |
line | stmt | bran | cond | sub | pod | time | code |
---|---|---|---|---|---|---|---|
1 | package Graph::Undirected::Hamiltonicity::Transforms; | ||||||
2 | |||||||
3 | 15 | 15 | 132248 | use Modern::Perl; | |||
15 | 122 | ||||||
15 | 87 | ||||||
4 | 15 | 15 | 1683 | use Carp; | |||
15 | 29 | ||||||
15 | 916 | ||||||
5 | |||||||
6 | 15 | 15 | 4692 | use Graph::Undirected; | |||
15 | 392632 | ||||||
15 | 411 | ||||||
7 | 15 | 15 | 4761 | use Graph::Undirected::Hamiltonicity::Output qw(:all); | |||
15 | 40 | ||||||
15 | 2188 | ||||||
8 | |||||||
9 | 15 | 15 | 106 | use Exporter qw(import); | |||
15 | 30 | ||||||
15 | 25276 | ||||||
10 | |||||||
11 | our @EXPORT_OK = qw( | ||||||
12 | &add_random_edges | ||||||
13 | &delete_cycle_closing_edges | ||||||
14 | &delete_non_required_neighbors | ||||||
15 | &get_common_neighbors | ||||||
16 | &get_required_graph | ||||||
17 | &get_random_isomorph | ||||||
18 | &string_to_graph | ||||||
19 | &swap_vertices | ||||||
20 | ); | ||||||
21 | |||||||
22 | our %EXPORT_TAGS = ( all => \@EXPORT_OK ); | ||||||
23 | |||||||
24 | ########################################################################## | ||||||
25 | |||||||
26 | # The "required graph" contains the same vertices as the original graph, | ||||||
27 | # but with only the edges incident on vertices of degree == 2. | ||||||
28 | |||||||
29 | sub get_required_graph { | ||||||
30 | 204 | 204 | 1 | 530 | my ($g) = @_; | ||
31 | |||||||
32 | 204 | 810 | output( "Beginning a sweep to mark all edges adjacent to degree 2 " | ||||
33 | . "vertices as required: " ); |
||||||
34 | |||||||
35 | 204 | 880 | my $g1 = $g->deep_copy_graph(); | ||||
36 | 204 | 347931 | output($g1); | ||||
37 | |||||||
38 | 204 | 725 | my @vertices = $g1->vertices(); | ||||
39 | 204 | 4747 | my $required_graph = Graph::Undirected->new( vertices => \@vertices ); | ||||
40 | |||||||
41 | 204 | 75968 | foreach my $vertex (@vertices) { | ||||
42 | 2659 | 6373 | my $degree = $g1->degree($vertex); | ||||
43 | 2659 | 100 | 679474 | if ( $degree != 2 ) { | |||
44 | 1442 | 5654 | output("Vertex $vertex : Degree=[$degree] ...skipping. "); |
||||
45 | 1442 | 3271 | next; | ||||
46 | } | ||||||
47 | |||||||
48 | 1217 | 4882 | output("Vertex $vertex : Degree=[$degree] "); | ||||
49 | 1217 | 2945 | output("
|
||||
50 | 1217 | 3091 | foreach my $neighbor_vertex ( $g1->neighbors($vertex) ) { | ||||
51 | 2434 | 86988 | $required_graph->add_edge( $vertex, $neighbor_vertex ); | ||||
52 | |||||||
53 | 2434 | 100 | 231526 | if ( $g1->get_edge_attribute( $vertex, $neighbor_vertex, | |||
54 | 'required') ) { | ||||||
55 | 1736 | 161042 | output( " |
||||
56 | . "marked required" ); | ||||||
57 | 1736 | 3808 | next; | ||||
58 | } | ||||||
59 | |||||||
60 | 698 | 66878 | $g1->set_edge_attribute($vertex, $neighbor_vertex, | ||||
61 | 'required', 1); | ||||||
62 | 698 | 71444 | output( " |
||||
63 | . "as required" ); | ||||||
64 | } | ||||||
65 | 1217 | 2728 | output(""); | ||||
66 | } | ||||||
67 | |||||||
68 | 204 | 100 | 788 | if ( $required_graph->edges() ) { | |||
69 | 192 | 8026 | output("required graph:"); | ||||
70 | 192 | 715 | output( $required_graph, { required => 1 } ); | ||||
71 | } else { | ||||||
72 | 12 | 372 | output("The required graph has no edges. "); |
||||
73 | } | ||||||
74 | |||||||
75 | 204 | 1375 | return ( $required_graph, $g1 ); | ||||
76 | } | ||||||
77 | |||||||
78 | ########################################################################## | ||||||
79 | |||||||
80 | # For each required walk, delete the edge connecting its endpoints, | ||||||
81 | # as such an edge would make the graph non-Hamiltonian, and therefore | ||||||
82 | # the edge can never be part of a Hamiltonian cycle. | ||||||
83 | |||||||
84 | sub delete_cycle_closing_edges { | ||||||
85 | 121 | 121 | 0 | 608 | output("Entering delete_cycle_closing_edges() "); |
||
86 | 121 | 325 | my ($g, $required_graph) = @_; | ||||
87 | 121 | 272 | my $deleted_edges = 0; | ||||
88 | 121 | 284 | my $g1; | ||||
89 | my %eliminated; | ||||||
90 | |||||||
91 | 121 | 460 | foreach my $vertex ( $required_graph->vertices() ) { | ||||
92 | 1705 | 100 | 224684 | next unless $required_graph->degree($vertex) == 1; | |||
93 | 492 | 50 | 92367 | next if $eliminated{$vertex}++; | |||
94 | |||||||
95 | 492 | 1672 | my @reachable = $required_graph->all_reachable($vertex); | ||||
96 | |||||||
97 | 492 | 268890 | my ( $other_vertex ) = grep { $required_graph->degree($_) == 1 } @reachable; | ||||
1874 | 286352 | ||||||
98 | 492 | 66 | 100371 | $g1 //= $g->deep_copy_graph(); | |||
99 | 492 | 100 | 158742 | next unless $g1->has_edge($vertex, $other_vertex); | |||
100 | 58 | 2762 | $g1->delete_edge($vertex, $other_vertex); | ||||
101 | 58 | 6616 | $required_graph->delete_edge($vertex, $other_vertex); | ||||
102 | 58 | 2389 | $deleted_edges++; | ||||
103 | |||||||
104 | 58 | 387 | output( "Deleted edge $vertex=$other_vertex" | ||||
105 | . ", between endpoints of a required walk. " ); |
||||||
106 | } | ||||||
107 | |||||||
108 | 121 | 100 | 14990 | if ( $deleted_edges ) { | |||
109 | 47 | 100 | 191 | my $s = $deleted_edges == 1 ? '' : 's'; | |||
110 | 47 | 346 | output("Shrank the graph by removing $deleted_edges edge$s. "); |
||||
111 | 47 | 374 | return ( $deleted_edges, $g1 ); | ||||
112 | } else { | ||||||
113 | 74 | 419 | output("Did not shrink the graph. "); |
||||
114 | 74 | 3470 | return ( $deleted_edges, $g ); | ||||
115 | } | ||||||
116 | } | ||||||
117 | |||||||
118 | ########################################################################## | ||||||
119 | |||||||
120 | sub delete_non_required_neighbors { | ||||||
121 | 123 | 123 | 1 | 3295 | output("Entering delete_non_required_neighbors() "); |
||
122 | |||||||
123 | 123 | 335 | my ( $g, $required_graph ) = @_; | ||||
124 | 123 | 261 | my $g1; | ||||
125 | 123 | 245 | my $deleted_edges = 0; | ||||
126 | 123 | 842 | foreach my $required_vertex ( $required_graph->vertices() ) { | ||||
127 | 1702 | 100 | 142742 | next if $required_graph->degree($required_vertex) != 2; | |||
128 | 702 | 152690 | foreach my $neighbor_vertex ( $g->neighbors($required_vertex) ) { | ||||
129 | 1557 | 49320 | my $required = | ||||
130 | $g->get_edge_attribute( $required_vertex, | ||||||
131 | $neighbor_vertex, 'required' ); | ||||||
132 | 1557 | 100 | 137764 | next if $required; | |||
133 | ### Clone graph lazily | ||||||
134 | 153 | 66 | 680 | $g1 //= $g->deep_copy_graph(); | |||
135 | |||||||
136 | next | ||||||
137 | 153 | 100 | 111895 | unless $g1->has_edge( | |||
138 | $required_vertex, $neighbor_vertex ); | ||||||
139 | |||||||
140 | 147 | 6070 | $g1->delete_edge( $required_vertex, $neighbor_vertex ); | ||||
141 | 147 | 14142 | $deleted_edges++; | ||||
142 | 147 | 818 | output( "Deleted edge $required_vertex=$neighbor_vertex " | ||||
143 | . "because vertex $required_vertex has degree==2 " | ||||||
144 | . "in the required graph. " ); |
||||||
145 | } | ||||||
146 | } | ||||||
147 | |||||||
148 | 123 | 100 | 13128 | if ( $deleted_edges ) { | |||
149 | 60 | 100 | 263 | my $s = $deleted_edges == 1 ? '' : 's'; | |||
150 | 60 | 341 | output("Shrank the graph by removing $deleted_edges edge$s. "); |
||||
151 | 60 | 339 | return ( $deleted_edges, $g1 ); | ||||
152 | } else { | ||||||
153 | 63 | 317 | output("Did not shrink the graph. "); |
||||
154 | 63 | 315 | return ( $deleted_edges, $g ); | ||||
155 | } | ||||||
156 | } | ||||||
157 | |||||||
158 | ########################################################################## | ||||||
159 | |||||||
160 | sub swap_vertices { | ||||||
161 | 3836 | 3836 | 1 | 12622 | my ( $g, $vertex_1, $vertex_2 ) = @_; | ||
162 | 3836 | 11164 | my $g1 = $g->deep_copy_graph(); | ||||
163 | |||||||
164 | my %common_neighbors = | ||||||
165 | 3836 | 4224922 | %{ get_common_neighbors( $g1, $vertex_1, $vertex_2 ) }; | ||||
3836 | 10992 | ||||||
166 | |||||||
167 | my @vertex_1_neighbors = | ||||||
168 | 3836 | 11121 | grep { $_ != $vertex_2 } $g1->neighbors($vertex_1); | ||||
8093 | 268328 | ||||||
169 | my @vertex_2_neighbors = | ||||||
170 | 3836 | 10009 | grep { $_ != $vertex_1 } $g1->neighbors($vertex_2); | ||||
8087 | 260413 | ||||||
171 | |||||||
172 | 3836 | 9082 | foreach my $neighbor_vertex (@vertex_1_neighbors) { | ||||
173 | 7352 | 100 | 340897 | next if $common_neighbors{$neighbor_vertex}; | |||
174 | 6523 | 19204 | $g1->delete_edge( $neighbor_vertex, $vertex_1 ); | ||||
175 | 6523 | 596971 | $g1->add_edge( $neighbor_vertex, $vertex_2 ); | ||||
176 | } | ||||||
177 | |||||||
178 | 3836 | 375052 | foreach my $neighbor_vertex (@vertex_2_neighbors) { | ||||
179 | 7346 | 100 | 322615 | next if $common_neighbors{$neighbor_vertex}; | |||
180 | 6517 | 19291 | $g1->delete_edge( $neighbor_vertex, $vertex_2 ); | ||||
181 | 6517 | 540799 | $g1->add_edge( $neighbor_vertex, $vertex_1 ); | ||||
182 | } | ||||||
183 | |||||||
184 | 3836 | 401530 | return $g1; | ||||
185 | } | ||||||
186 | |||||||
187 | ########################################################################## | ||||||
188 | |||||||
189 | sub get_common_neighbors { | ||||||
190 | 3843 | 3843 | 1 | 16128 | my ( $g, $vertex_1, $vertex_2 ) = @_; | ||
191 | 3843 | 7217 | my %common_neighbors; | ||||
192 | my %vertex_1_neighbors; | ||||||
193 | 3843 | 11896 | foreach my $neighbor_vertex ( $g->neighbors($vertex_1) ) { | ||||
194 | 8121 | 351675 | $vertex_1_neighbors{$neighbor_vertex} = 1; | ||||
195 | } | ||||||
196 | |||||||
197 | 3843 | 10418 | foreach my $neighbor_vertex ( $g->neighbors($vertex_2) ) { | ||||
198 | 8110 | 100 | 275677 | next unless $vertex_1_neighbors{$neighbor_vertex}; | |||
199 | 839 | 1853 | $common_neighbors{$neighbor_vertex} = 1; | ||||
200 | } | ||||||
201 | |||||||
202 | 3843 | 13688 | return \%common_neighbors; | ||||
203 | } | ||||||
204 | |||||||
205 | ########################################################################## | ||||||
206 | |||||||
207 | # Takes a string representation of a Graph::Undirected | ||||||
208 | # The string is the same format as the result of calling the stringify() | ||||||
209 | # method on a Graph::Undirected object. | ||||||
210 | # | ||||||
211 | # Returns a Graph::Undirected object, constructed from its string form. | ||||||
212 | |||||||
213 | sub string_to_graph { | ||||||
214 | 74 | 74 | 1 | 120865 | my ($string) = @_; | ||
215 | 74 | 182 | my %vertices; | ||||
216 | my @edges; | ||||||
217 | |||||||
218 | 74 | 379 | foreach my $chunk ( split( /\,/, $string ) ) { | ||||
219 | 978 | 100 | 1900 | if ( $chunk =~ /=/ ) { | |||
220 | 975 | 1806 | my @endpoints = map {s/\b0+([1-9])/$1/gr} | ||||
1950 | 3638 | ||||||
221 | split( /=/, $chunk ); | ||||||
222 | |||||||
223 | 975 | 100 | 2159 | next if $endpoints[0] == $endpoints[1]; | |||
224 | 974 | 1591 | push @edges, \@endpoints; | ||||
225 | 974 | 1460 | $vertices{ $endpoints[0] } = 1; | ||||
226 | 974 | 1749 | $vertices{ $endpoints[1] } = 1; | ||||
227 | } else { | ||||||
228 | 3 | 17 | $vertices{$chunk} = 1; | ||||
229 | } | ||||||
230 | } | ||||||
231 | |||||||
232 | 74 | 305 | my @vertices = keys %vertices; | ||||
233 | 74 | 431 | my $g = Graph::Undirected->new( vertices => \@vertices ); | ||||
234 | |||||||
235 | 74 | 50603 | foreach my $edge_ref (@edges) { | ||||
236 | 974 | 100 | 186203 | $g->add_edge(@$edge_ref) unless $g->has_edge(@$edge_ref); | |||
237 | } | ||||||
238 | |||||||
239 | 74 | 10021 | return $g; | ||||
240 | } | ||||||
241 | |||||||
242 | ########################################################################## | ||||||
243 | |||||||
244 | # Takes a Graph::Undirected ( $g ) | ||||||
245 | # | ||||||
246 | # Returns a Graph::Undirected ( $g1 ) which is an isomorph of $g | ||||||
247 | |||||||
248 | sub get_random_isomorph { | ||||||
249 | 26 | 26 | 1 | 79 | my ($g) = @_; | ||
250 | |||||||
251 | # everyday i'm shufflin' | ||||||
252 | |||||||
253 | 26 | 100 | my $g1 = $g->deep_copy_graph(); | ||||
254 | 26 | 98994 | my $v = scalar( $g1->vertices() ); | ||||
255 | |||||||
256 | 26 | 509 | my $max_times_to_shuffle = $v * $v; | ||||
257 | 26 | 57 | my $shuffles = 0; | ||||
258 | 26 | 97 | while ( $shuffles < $max_times_to_shuffle ) { | ||||
259 | 4131 | 12245 | my $v1 = int( rand($v) ); | ||||
260 | 4131 | 6907 | my $v2 = int( rand($v) ); | ||||
261 | |||||||
262 | 4131 | 100 | 9618 | next if $v1 == $v2; | |||
263 | |||||||
264 | 3828 | 9223 | $g1 = swap_vertices( $g1, $v1, $v2 ); | ||||
265 | 3828 | 12427 | $shuffles++; | ||||
266 | } | ||||||
267 | |||||||
268 | 26 | 560 | return $g1; | ||||
269 | } | ||||||
270 | |||||||
271 | ############################################################################## | ||||||
272 | |||||||
273 | sub add_random_edges { | ||||||
274 | 69 | 69 | 1 | 671 | my ( $g, $edges_to_add ) = @_; | ||
275 | |||||||
276 | 69 | 282 | my $e = scalar( $g->edges() ); | ||||
277 | 69 | 2926 | my $v = scalar( $g->vertices() ); | ||||
278 | 69 | 1347 | my $max_edges = ( $v * $v - $v ) / 2; | ||||
279 | |||||||
280 | 69 | 50 | 262 | if ( ($e + $edges_to_add) > $max_edges ) { | |||
281 | 0 | 0 | croak "Can only add up to: ", $max_edges - $e, | ||||
282 | " edges. NOT [$edges_to_add]; e=[$e]\n"; | ||||||
283 | } | ||||||
284 | |||||||
285 | 69 | 235 | my $g1 = $g->deep_copy_graph(); | ||||
286 | 69 | 189469 | my $added_edges = 0; | ||||
287 | 69 | 289 | while ( $added_edges < $edges_to_add ) { | ||||
288 | 3950 | 58733 | my $v1 = int( rand($v) ); | ||||
289 | 3950 | 5278 | my $v2 = int( rand($v) ); | ||||
290 | |||||||
291 | 3950 | 100 | 7104 | next if $v1 == $v2; | |||
292 | 3678 | 100 | 7215 | next if $g1->has_edge( $v1, $v2 ); | |||
293 | |||||||
294 | 2162 | 79254 | $g1->add_edge( $v1, $v2 ); | ||||
295 | 2162 | 229896 | $added_edges++; | ||||
296 | } | ||||||
297 | |||||||
298 | 69 | 959 | return $g1; | ||||
299 | } | ||||||
300 | |||||||
301 | ############################################################################## | ||||||
302 | |||||||
303 | |||||||
304 | 1; # End of Graph::Undirected::Hamiltonicity::Transforms |