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