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 | 127971 | use Modern::Perl; | |||
15 | 79 | ||||||
15 | 93 | ||||||
4 | 15 | 15 | 1647 | use Carp; | |||
15 | 30 | ||||||
15 | 708 | ||||||
5 | |||||||
6 | 15 | 15 | 4682 | use Graph::Undirected; | |||
15 | 388737 | ||||||
15 | 435 | ||||||
7 | 15 | 15 | 5154 | use Graph::Undirected::Hamiltonicity::Output qw(:all); | |||
15 | 37 | ||||||
15 | 2208 | ||||||
8 | |||||||
9 | 15 | 15 | 101 | use Exporter qw(import); | |||
15 | 29 | ||||||
15 | 24896 | ||||||
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 | 211 | 211 | 1 | 541 | my ($g) = @_; | ||
31 | |||||||
32 | 211 | 900 | output( "Beginning a sweep to mark all edges adjacent to degree 2 " | ||||
33 | . "vertices as required: " ); |
||||||
34 | |||||||
35 | 211 | 896 | my $g1 = $g->deep_copy_graph(); | ||||
36 | 211 | 354491 | output($g1); | ||||
37 | |||||||
38 | 211 | 798 | my @vertices = $g1->vertices(); | ||||
39 | 211 | 4927 | my $required_graph = Graph::Undirected->new( vertices => \@vertices ); | ||||
40 | |||||||
41 | 211 | 81774 | foreach my $vertex (@vertices) { | ||||
42 | 2820 | 6657 | my $degree = $g1->degree($vertex); | ||||
43 | 2820 | 100 | 727657 | if ( $degree != 2 ) { | |||
44 | 1562 | 6147 | output("Vertex $vertex : Degree=[$degree] ...skipping. "); |
||||
45 | 1562 | 3471 | next; | ||||
46 | } | ||||||
47 | |||||||
48 | 1258 | 5282 | output("Vertex $vertex : Degree=[$degree] "); | ||||
49 | 1258 | 3161 | output("
|
||||
50 | 1258 | 2985 | foreach my $neighbor_vertex ( $g1->neighbors($vertex) ) { | ||||
51 | 2516 | 90406 | $required_graph->add_edge( $vertex, $neighbor_vertex ); | ||||
52 | |||||||
53 | 2516 | 100 | 240826 | if ( $g1->get_edge_attribute( $vertex, $neighbor_vertex, | |||
54 | 'required') ) { | ||||||
55 | 1748 | 162266 | output( " |
||||
56 | . "marked required" ); | ||||||
57 | 1748 | 4040 | next; | ||||
58 | } | ||||||
59 | |||||||
60 | 768 | 73871 | $g1->set_edge_attribute($vertex, $neighbor_vertex, | ||||
61 | 'required', 1); | ||||||
62 | 768 | 79576 | output( " |
||||
63 | . "as required" ); | ||||||
64 | } | ||||||
65 | 1258 | 2843 | output(""); | ||||
66 | } | ||||||
67 | |||||||
68 | 211 | 100 | 804 | if ( $required_graph->edges() ) { | |||
69 | 199 | 8265 | output("required graph:"); | ||||
70 | 199 | 737 | output( $required_graph, { required => 1 } ); | ||||
71 | } else { | ||||||
72 | 12 | 437 | output("The required graph has no edges. "); |
||||
73 | } | ||||||
74 | |||||||
75 | 211 | 1406 | 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 | 119 | 119 | 0 | 584 | output("Entering delete_cycle_closing_edges() "); |
||
86 | 119 | 355 | my ($g, $required_graph) = @_; | ||||
87 | 119 | 258 | my $deleted_edges = 0; | ||||
88 | 119 | 306 | my $g1; | ||||
89 | my %eliminated; | ||||||
90 | |||||||
91 | 119 | 448 | foreach my $vertex ( $required_graph->vertices() ) { | ||||
92 | 1713 | 100 | 226776 | next unless $required_graph->degree($vertex) == 1; | |||
93 | 482 | 50 | 91128 | next if $eliminated{$vertex}++; | |||
94 | |||||||
95 | 482 | 1552 | my @reachable = $required_graph->all_reachable($vertex); | ||||
96 | |||||||
97 | 482 | 276535 | my ( $other_vertex ) = grep { $required_graph->degree($_) == 1 } @reachable; | ||||
1916 | 302027 | ||||||
98 | 482 | 66 | 98511 | $g1 //= $g->deep_copy_graph(); | |||
99 | 482 | 100 | 160863 | next unless $g1->has_edge($vertex, $other_vertex); | |||
100 | 57 | 2690 | $g1->delete_edge($vertex, $other_vertex); | ||||
101 | 57 | 6076 | $required_graph->delete_edge($vertex, $other_vertex); | ||||
102 | 57 | 2308 | $deleted_edges++; | ||||
103 | |||||||
104 | 57 | 369 | output( "Deleted edge $vertex=$other_vertex" | ||||
105 | . ", between endpoints of a required walk. " ); |
||||||
106 | } | ||||||
107 | |||||||
108 | 119 | 100 | 17686 | if ( $deleted_edges ) { | |||
109 | 44 | 100 | 180 | my $s = $deleted_edges == 1 ? '' : 's'; | |||
110 | 44 | 315 | output("Shrank the graph by removing $deleted_edges edge$s. "); |
||||
111 | 44 | 307 | return ( $deleted_edges, $g1 ); | ||||
112 | } else { | ||||||
113 | 75 | 424 | output("Did not shrink the graph. "); |
||||
114 | 75 | 3136 | return ( $deleted_edges, $g ); | ||||
115 | } | ||||||
116 | } | ||||||
117 | |||||||
118 | ########################################################################## | ||||||
119 | |||||||
120 | sub delete_non_required_neighbors { | ||||||
121 | 121 | 121 | 1 | 3715 | output("Entering delete_non_required_neighbors() "); |
||
122 | |||||||
123 | 121 | 298 | my ( $g, $required_graph ) = @_; | ||||
124 | 121 | 242 | my $g1; | ||||
125 | 121 | 248 | my $deleted_edges = 0; | ||||
126 | 121 | 459 | foreach my $required_vertex ( $required_graph->vertices() ) { | ||||
127 | 1710 | 100 | 143321 | next if $required_graph->degree($required_vertex) != 2; | |||
128 | 728 | 160261 | foreach my $neighbor_vertex ( $g->neighbors($required_vertex) ) { | ||||
129 | 1635 | 51675 | my $required = | ||||
130 | $g->get_edge_attribute( $required_vertex, | ||||||
131 | $neighbor_vertex, 'required' ); | ||||||
132 | 1635 | 100 | 145474 | next if $required; | |||
133 | ### Clone graph lazily | ||||||
134 | 179 | 66 | 756 | $g1 //= $g->deep_copy_graph(); | |||
135 | |||||||
136 | next | ||||||
137 | 179 | 100 | 123297 | unless $g1->has_edge( | |||
138 | $required_vertex, $neighbor_vertex ); | ||||||
139 | |||||||
140 | 172 | 7077 | $g1->delete_edge( $required_vertex, $neighbor_vertex ); | ||||
141 | 172 | 16500 | $deleted_edges++; | ||||
142 | 172 | 1046 | output( "Deleted edge $required_vertex=$neighbor_vertex " | ||||
143 | . "because vertex $required_vertex has degree==2 " | ||||||
144 | . "in the required graph. " ); |
||||||
145 | } | ||||||
146 | } | ||||||
147 | |||||||
148 | 121 | 100 | 10068 | if ( $deleted_edges ) { | |||
149 | 69 | 100 | 244 | my $s = $deleted_edges == 1 ? '' : 's'; | |||
150 | 69 | 386 | output("Shrank the graph by removing $deleted_edges edge$s. "); |
||||
151 | 69 | 431 | return ( $deleted_edges, $g1 ); | ||||
152 | } else { | ||||||
153 | 52 | 241 | output("Did not shrink the graph. "); |
||||
154 | 52 | 253 | return ( $deleted_edges, $g ); | ||||
155 | } | ||||||
156 | } | ||||||
157 | |||||||
158 | ########################################################################## | ||||||
159 | |||||||
160 | sub swap_vertices { | ||||||
161 | 3836 | 3836 | 1 | 10920 | my ( $g, $vertex_1, $vertex_2 ) = @_; | ||
162 | 3836 | 9177 | my $g1 = $g->deep_copy_graph(); | ||||
163 | |||||||
164 | my %common_neighbors = | ||||||
165 | 3836 | 4082891 | %{ get_common_neighbors( $g1, $vertex_1, $vertex_2 ) }; | ||||
3836 | 8838 | ||||||
166 | |||||||
167 | my @vertex_1_neighbors = | ||||||
168 | 3836 | 9900 | grep { $_ != $vertex_2 } $g1->neighbors($vertex_1); | ||||
8089 | 268364 | ||||||
169 | my @vertex_2_neighbors = | ||||||
170 | 3836 | 8762 | grep { $_ != $vertex_1 } $g1->neighbors($vertex_2); | ||||
8078 | 262422 | ||||||
171 | |||||||
172 | 3836 | 7857 | foreach my $neighbor_vertex (@vertex_1_neighbors) { | ||||
173 | 7384 | 100 | 337854 | next if $common_neighbors{$neighbor_vertex}; | |||
174 | 6581 | 18278 | $g1->delete_edge( $neighbor_vertex, $vertex_1 ); | ||||
175 | 6581 | 589706 | $g1->add_edge( $neighbor_vertex, $vertex_2 ); | ||||
176 | } | ||||||
177 | |||||||
178 | 3836 | 370951 | foreach my $neighbor_vertex (@vertex_2_neighbors) { | ||||
179 | 7373 | 100 | 323305 | next if $common_neighbors{$neighbor_vertex}; | |||
180 | 6570 | 17315 | $g1->delete_edge( $neighbor_vertex, $vertex_2 ); | ||||
181 | 6570 | 546726 | $g1->add_edge( $neighbor_vertex, $vertex_1 ); | ||||
182 | } | ||||||
183 | |||||||
184 | 3836 | 397293 | return $g1; | ||||
185 | } | ||||||
186 | |||||||
187 | ########################################################################## | ||||||
188 | |||||||
189 | sub get_common_neighbors { | ||||||
190 | 3843 | 3843 | 1 | 14273 | my ( $g, $vertex_1, $vertex_2 ) = @_; | ||
191 | 3843 | 6293 | my %common_neighbors; | ||||
192 | my %vertex_1_neighbors; | ||||||
193 | 3843 | 9873 | foreach my $neighbor_vertex ( $g->neighbors($vertex_1) ) { | ||||
194 | 8117 | 335838 | $vertex_1_neighbors{$neighbor_vertex} = 1; | ||||
195 | } | ||||||
196 | |||||||
197 | 3843 | 9271 | foreach my $neighbor_vertex ( $g->neighbors($vertex_2) ) { | ||||
198 | 8101 | 100 | 276558 | next unless $vertex_1_neighbors{$neighbor_vertex}; | |||
199 | 813 | 1781 | $common_neighbors{$neighbor_vertex} = 1; | ||||
200 | } | ||||||
201 | |||||||
202 | 3843 | 13361 | 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 | 107986 | my ($string) = @_; | ||
215 | 74 | 170 | my %vertices; | ||||
216 | my @edges; | ||||||
217 | |||||||
218 | 74 | 350 | foreach my $chunk ( split( /\,/, $string ) ) { | ||||
219 | 978 | 100 | 1929 | if ( $chunk =~ /=/ ) { | |||
220 | 975 | 1881 | my @endpoints = map {s/\b0+([1-9])/$1/gr} | ||||
1950 | 3679 | ||||||
221 | split( /=/, $chunk ); | ||||||
222 | |||||||
223 | 975 | 100 | 2190 | next if $endpoints[0] == $endpoints[1]; | |||
224 | 974 | 1592 | push @edges, \@endpoints; | ||||
225 | 974 | 1523 | $vertices{ $endpoints[0] } = 1; | ||||
226 | 974 | 1758 | $vertices{ $endpoints[1] } = 1; | ||||
227 | } else { | ||||||
228 | 3 | 9 | $vertices{$chunk} = 1; | ||||
229 | } | ||||||
230 | } | ||||||
231 | |||||||
232 | 74 | 299 | my @vertices = keys %vertices; | ||||
233 | 74 | 392 | my $g = Graph::Undirected->new( vertices => \@vertices ); | ||||
234 | |||||||
235 | 74 | 49488 | foreach my $edge_ref (@edges) { | ||||
236 | 974 | 100 | 187693 | $g->add_edge(@$edge_ref) unless $g->has_edge(@$edge_ref); | |||
237 | } | ||||||
238 | |||||||
239 | 74 | 10337 | 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 | 103 | my $g1 = $g->deep_copy_graph(); | ||||
254 | 26 | 101813 | my $v = scalar( $g1->vertices() ); | ||||
255 | |||||||
256 | 26 | 465 | my $max_times_to_shuffle = $v * $v; | ||||
257 | 26 | 67 | my $shuffles = 0; | ||||
258 | 26 | 88 | while ( $shuffles < $max_times_to_shuffle ) { | ||||
259 | 4163 | 9257 | my $v1 = int( rand($v) ); | ||||
260 | 4163 | 6168 | my $v2 = int( rand($v) ); | ||||
261 | |||||||
262 | 4163 | 100 | 9184 | next if $v1 == $v2; | |||
263 | |||||||
264 | 3828 | 8190 | $g1 = swap_vertices( $g1, $v1, $v2 ); | ||||
265 | 3828 | 10498 | $shuffles++; | ||||
266 | } | ||||||
267 | |||||||
268 | 26 | 562 | return $g1; | ||||
269 | } | ||||||
270 | |||||||
271 | ############################################################################## | ||||||
272 | |||||||
273 | sub add_random_edges { | ||||||
274 | 70 | 70 | 1 | 756 | my ( $g, $edges_to_add ) = @_; | ||
275 | |||||||
276 | 70 | 254 | my $e = scalar( $g->edges() ); | ||||
277 | 70 | 2660 | my $v = scalar( $g->vertices() ); | ||||
278 | 70 | 1245 | my $max_edges = ( $v * $v - $v ) / 2; | ||||
279 | |||||||
280 | 70 | 50 | 231 | 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 | 70 | 212 | my $g1 = $g->deep_copy_graph(); | ||||
286 | 70 | 184323 | my $added_edges = 0; | ||||
287 | 70 | 258 | while ( $added_edges < $edges_to_add ) { | ||||
288 | 3234 | 38988 | my $v1 = int( rand($v) ); | ||||
289 | 3234 | 4362 | my $v2 = int( rand($v) ); | ||||
290 | |||||||
291 | 3234 | 100 | 5468 | next if $v1 == $v2; | |||
292 | 2967 | 100 | 5601 | next if $g1->has_edge( $v1, $v2 ); | |||
293 | |||||||
294 | 1953 | 70707 | $g1->add_edge( $v1, $v2 ); | ||||
295 | 1953 | 205980 | $added_edges++; | ||||
296 | } | ||||||
297 | |||||||
298 | 70 | 582 | return $g1; | ||||
299 | } | ||||||
300 | |||||||
301 | ############################################################################## | ||||||
302 | |||||||
303 | |||||||
304 | 1; # End of Graph::Undirected::Hamiltonicity::Transforms |