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