| 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 |