File Coverage

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( "
  • $vertex=$neighbor_vertex is already "
  • 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( "
  • Marking $vertex=$neighbor_vertex "
  • 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