diff mayachemtools/lib/Graph/Path.pm @ 0:73ae111cf86f draft

Uploaded
author deepakjadmin
date Wed, 20 Jan 2016 11:55:01 -0500
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mayachemtools/lib/Graph/Path.pm	Wed Jan 20 11:55:01 2016 -0500
@@ -0,0 +1,769 @@
+package Graph::Path;
+#
+# $RCSfile: Path.pm,v $
+# $Date: 2015/02/28 20:49:06 $
+# $Revision: 1.25 $
+#
+# Author: Manish Sud <msud@san.rr.com>
+#
+# Copyright (C) 2015 Manish Sud. All rights reserved.
+#
+# This file is part of MayaChemTools.
+#
+# MayaChemTools is free software; you can redistribute it and/or modify it under
+# the terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation; either version 3 of the License, or (at your option) any
+# later version.
+#
+# MayaChemTools is distributed in the hope that it will be useful, but without
+# any warranty; without even the implied warranty of merchantability of fitness
+# for a particular purpose.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with MayaChemTools; if not, see <http://www.gnu.org/licenses/> or
+# write to the Free Software Foundation Inc., 59 Temple Place, Suite 330,
+# Boston, MA, 02111-1307, USA.
+#
+
+use strict;
+use Carp;
+use Exporter;
+use Storable ();
+use Scalar::Util ();
+
+use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
+
+@ISA = qw(Exporter);
+@EXPORT = qw();
+@EXPORT_OK = qw();
+
+%EXPORT_TAGS = (all  => [@EXPORT, @EXPORT_OK]);
+
+# Setup class variables...
+my($ClassName, $ObjectID);
+_InitializeClass();
+
+# Overload Perl functions...
+use overload '""' => 'StringifyPath',
+
+  '==' => '_PathEqualOperator',
+  'eq' => '_PathEqualOperator',
+
+  'fallback' => undef;
+
+# Class constructor...
+sub new {
+  my($Class, @VertexIDs) = @_;
+
+  # Initialize object...
+  my $This = {};
+  bless $This, ref($Class) || $Class;
+  $This->_InitializePath();
+
+  if (@VertexIDs) { $This->AddVertices(@VertexIDs); }
+
+  return $This;
+}
+
+# Initialize object data...
+#
+sub _InitializePath {
+  my($This) = @_;
+
+  @{$This->{Vertices}} = ();
+}
+
+# Initialize class ...
+sub _InitializeClass {
+  #Class name...
+  $ClassName = __PACKAGE__;
+}
+
+# Add a vertex to path after the end vertex...
+#
+sub AddVertex {
+  my($This, $VertexID) = @_;
+
+  if (!defined $VertexID ) {
+    carp "Warning: ${ClassName}->AddVertex: No vertex added: Vertex ID must be specified...";
+    return undef;
+  }
+  push @{$This->{Vertices}}, $VertexID;
+
+  return $This;
+}
+
+# Add vertices to the path after the end vertex...
+#
+sub AddVertices {
+  my($This, @VertexIDs) = @_;
+
+  if (!@VertexIDs) {
+    carp "Warning: ${ClassName}->AddVertices: No vertices added: Vertices list is empty...";
+    return undef;
+  }
+  push @{$This->{Vertices}}, @VertexIDs;
+
+  return $This;
+}
+
+# Add a vertex to path after the end vertex...
+#
+sub PushVertex {
+  my($This, $VertexID) = @_;
+
+  return $This->AddVertex($VertexID);
+}
+
+# Add vertices to the path after the end vertex...
+#
+sub PushVertices {
+  my($This, @VertexIDs) = @_;
+
+  return $This->AddVertices(@VertexIDs);
+}
+
+# Remove end vertex from path...
+#
+sub PopVertex {
+  my($This) = @_;
+
+  if (!@{$This->{Vertices}}) {
+    carp "Warning: ${ClassName}->PopVertex: No vertex removed: Path is empty...";
+    return undef;
+  }
+  pop @{$This->{Vertices}};
+
+  return $This;
+}
+
+# Remove start vertex from path...
+#
+sub ShiftVertex {
+  my($This) = @_;
+
+  if (!@{$This->{Vertices}}) {
+    carp "Warning: ${ClassName}->ShiftVertex: No vertex removed: Path is empty...";
+    return undef;
+  }
+  shift @{$This->{Vertices}};
+
+  return $This;
+}
+
+# Add a vertex to path before the start vertex...
+#
+sub UnshiftVertex {
+  my($This, $VertexID) = @_;
+
+  if (!defined $VertexID ) {
+    carp "Warning: ${ClassName}->UnshiftVertex: No vertex added: Vertex ID must be specified...";
+    return undef;
+  }
+  unshift @{$This->{Vertices}}, $VertexID;
+
+  return $This;
+}
+
+# Add vertices to the path before the start vertex...
+#
+sub UnshiftVertices {
+  my($This, @VertexIDs) = @_;
+
+  if (!@VertexIDs) {
+    carp "Warning: ${ClassName}->UnshiftVertices: No vertices added: Vertices list is empty...";
+    return undef;
+  }
+  unshift @{$This->{Vertices}}, @VertexIDs;
+
+  return $This;
+}
+
+# Get length...
+#
+sub GetLength {
+  my($This) = @_;
+
+  return scalar @{$This->{Vertices}};
+}
+
+# Get start vertex...
+#
+sub GetStartVertex {
+  my($This) = @_;
+
+  if (!$This->GetLength()) {
+    return undef;
+  }
+  my($Index) = 0;
+  return $This->_GetVertex($Index);
+}
+
+# Get end vertex...
+#
+sub GetEndVertex {
+  my($This) = @_;
+
+  if (!$This->GetLength()) {
+    return undef;
+  }
+  my($Index);
+
+  $Index = $This->GetLength() - 1;
+  return $This->_GetVertex($Index);
+}
+
+# Get start and end vertices...
+#
+sub GetTerminalVertices {
+  my($This) = @_;
+
+  return ( $This->GetStartVertex(), $This->GetEndVertex() ),
+}
+
+# Get path vertices...
+#
+sub GetVertices {
+  my($This) = @_;
+
+  return wantarray ? @{$This->{Vertices}} : scalar @{$This->{Vertices}};
+}
+
+# Get a specific vertex from path with indicies starting from 0...
+#
+sub GetVertex {
+  my($This, $Index) = @_;
+
+  if ($Index < 0) {
+    croak "Error: ${ClassName}->GetValue: Index value must be a positive number...";
+  }
+  if ($Index >= $This->GetLength()) {
+    croak "Error: ${ClassName}->GetValue: Index vaue must be less than length of path...";
+  }
+  if (!$This->GetLength()) {
+    return undef;
+  }
+  return $This->_GetVertex($Index);
+}
+
+# Get a vertex...
+#
+sub _GetVertex {
+  my($This, $Index) = @_;
+
+  return $This->{Vertices}[$Index];
+}
+
+# Get path edges as pair of vertices or number of edges...
+#
+sub GetEdges {
+  my($This) = @_;
+
+  if ($This->GetLength < 1) {
+    return undef;
+  }
+  # Set up edges...
+  my($Index, $VertexID1, $VertexID2, @Vertices, @Edges);
+
+  @Edges = ();
+  for $Index (0 .. ($#{$This->{Vertices}} - 1) ) {
+    $VertexID1 = $This->{Vertices}[$Index];
+    $VertexID2 = $This->{Vertices}[$Index + 1];
+    push @Edges, ($VertexID1, $VertexID2);
+  }
+
+  return wantarray ? @Edges : ((scalar @Edges)/2);
+}
+
+# Is it a cycle?
+#
+sub IsCycle {
+  my($This) = @_;
+  my($StartVertex, $EndVertex);
+
+  ($StartVertex, $EndVertex) = $This->GetTerminalVertices();
+
+  return ($StartVertex == $EndVertex) ? 1 : 0;
+}
+
+# For a path to be an independent path, it must meet the following conditions:
+#   . All other vertices are unique.
+#
+sub IsIndependentPath {
+  my($This) = @_;
+
+  # Make sure it has at least two vertices...
+  if ($This->GetLength() < 2) {
+    return 0;
+  }
+
+  # Check frequency of occurence for non-terminal vertices...
+  my($VertexID, $IndependenceStatus, @Vertices, %VerticesMap);
+
+  @Vertices = $This->GetVertices();
+  shift @Vertices; pop @Vertices;
+
+  %VerticesMap = ();
+  $IndependenceStatus = 1;
+
+  VERTEXID: for $VertexID (@Vertices) {
+    if (exists $VerticesMap{$VertexID} ) {
+      $IndependenceStatus = 0;
+      last VERTEXID;
+    }
+    $VerticesMap{$VertexID} = $VertexID;
+  }
+  return $IndependenceStatus;
+}
+
+# For a path to be an independent cyclic path, it must meet the following conditions:
+#   . Termimal vertices are the same
+#   . All other vertices are unique.
+#
+sub IsIndependentCyclicPath {
+  my($This) = @_;
+
+  # Make sure it's a cycle...
+  if (!($This->GetLength() >= 3 && $This->IsCycle())) {
+    return 0;
+  }
+  return $This->IsIndependentPath();
+}
+
+# Is it a path object?
+sub IsPath ($) {
+  my($Object) = @_;
+
+  return _IsPath($Object);
+}
+
+# Copy path...
+#
+sub Copy {
+  my($This) = @_;
+  my($NewPath);
+
+  $NewPath = Storable::dclone($This);
+
+  return $NewPath;
+}
+
+# Reverse order of vertices in path...
+#
+sub Reverse {
+  my($This) = @_;
+  my(@VertexIDs);
+
+  @VertexIDs = (); push @VertexIDs, @{$This->{Vertices}};
+
+  @{$This->{Vertices}} = (); push @{$This->{Vertices}}, reverse @VertexIDs;
+
+  return $This;
+}
+
+# Get vertices common between two paths...
+#
+sub GetCommonVertices {
+  my($This, $Other) = @_;
+  my($VertexID, @CommonVertices, %OtherVerticesMap);
+
+  # Setup a vertices hash for a quick look up...
+  %OtherVerticesMap = ();
+  for $VertexID ($Other->GetVertices()) {
+    $OtherVerticesMap{$VertexID} = $VertexID;
+  }
+
+  @CommonVertices = ();
+  for $VertexID ($This->GetVertices()) {
+    if ($OtherVerticesMap{$VertexID}) {
+      push @CommonVertices, $VertexID
+    }
+  }
+  return wantarray ? @CommonVertices : scalar @CommonVertices;
+}
+
+# Join the existing path with a new path specifed using a path object of a list of
+# verticies.
+#
+sub Join {
+  my($This, @Values) = @_;
+
+  return $This->_Join(@Values);
+}
+
+# Join the existing path with a new path specifed using a path object at a specified
+# vertex.
+#
+sub JoinAtVertex {
+  my($This, $Other, $CenterVertexID) = @_;
+
+  # Make sure CenterVertexID is end vertex in This and start vertex in Other before
+  # joining them...
+  if ($This->GetEndVertex() != $CenterVertexID) {
+    $This->Reverse();
+  }
+  if ($Other->GetStartVertex() != $CenterVertexID) {
+    $Other->Reverse();
+  }
+  return $This->_Join($Other);
+}
+
+# Join the existing path with a new path specifed using a path object of a list of
+# verticies.
+#
+# Notes:
+#  . Paths must have a common terminal vertex.
+#  . Based on the common terminal vertex found, new path vertices are added to the
+#    current path in one of the four ways:
+#    . New path at end of current path with same vertices order : EndVertex = NewStartVertex
+#    . New path at end of current path with reversed vertices order: EndVertex = NewEndVertex
+#    . New path at front of current path with same vertices order: StartVertex = NewEndVertex
+#    . New path at front of current path with reversed vertices order: StartVertex = NewStartVertex
+#
+sub _Join {
+  my($This, @Values) = @_;
+
+  if (!@Values) {
+    return;
+  }
+
+  # Get a list of new vertex IDs..
+  my($NewPath, $FirstValue, $TypeOfFirstValue, @NewVertexIDs);
+
+  $NewPath = $This->Copy();
+
+  @NewVertexIDs = ();
+  $FirstValue = $Values[0];
+  $TypeOfFirstValue = ref $FirstValue;
+  if ($TypeOfFirstValue =~ /^(SCALAR|HASH|CODE|REF|GLOB)/) {
+    croak "Error: ${ClassName}->JoinPath: Trying to add vertices to path object with a reference to unsupported value format...";
+  }
+
+  if (_IsPath($FirstValue)) {
+    # It's another path object...
+    push @NewVertexIDs,  @{$FirstValue->{Vertices}};
+  }
+  elsif ($TypeOfFirstValue =~ /^ARRAY/) {
+    # It's array reference...
+    push @NewVertexIDs,  @{$FirstValue};
+  }
+  else {
+    # It's a list of values...
+    push @NewVertexIDs,  @Values;
+  }
+  my($StartVertex, $EndVertex, $NewStartVertex, $NewEndVertex);
+
+  ($StartVertex, $EndVertex) = $NewPath->GetTerminalVertices();
+  ($NewStartVertex, $NewEndVertex) = ($NewVertexIDs[0], $NewVertexIDs[$#NewVertexIDs]);
+
+  if (!($EndVertex == $NewStartVertex || $EndVertex == $NewEndVertex || $StartVertex == $NewEndVertex || $StartVertex == $NewStartVertex)) {
+    carp "Warning: ${ClassName}->JoinPath: Paths can't be joined: No common terminal vertex found...";
+    return undef;
+  }
+
+  if ($EndVertex == $NewStartVertex) {
+    # Take out EndVertex and add new path at the end...
+    pop @{$NewPath->{Vertices}};
+    push @{$NewPath->{Vertices}}, @NewVertexIDs;
+  }
+  elsif ($EndVertex == $NewEndVertex) {
+    # Take out EndVertex and add new path at the end with reversed vertex order...
+    pop @{$NewPath->{Vertices}};
+    push @{$NewPath->{Vertices}}, reverse @NewVertexIDs;
+  }
+  elsif ($StartVertex == $NewEndVertex) {
+    # Take out NewEndVertex and add new path at the front...
+    pop @NewVertexIDs;
+    unshift @{$NewPath->{Vertices}}, @NewVertexIDs;
+  }
+  elsif ($StartVertex == $NewStartVertex) {
+    # Take out NewStartVertex and add new path at the front...
+    shift @NewVertexIDs;
+    unshift @{$NewPath->{Vertices}}, reverse @NewVertexIDs;
+  }
+
+  return $NewPath,
+}
+
+# Compare two paths...
+#
+sub _PathEqualOperator {
+  my($This, $Other) = @_;
+
+  if (!(defined($This) && _IsPath($This) && defined($Other) && _IsPath($Other))) {
+    croak "Error: ${ClassName}->_PathEqualOperator: Path equal comparison failed: Both object must be paths...";
+  }
+
+  if ($This->GetLength() != $Other->GetLength()) {
+    return 0;
+  }
+  my($ThisID, $OtherID, $ReverseOtherID);
+
+  $ThisID = join('-', @{$This->{Vertices}});
+  $OtherID = join('-', @{$Other->{Vertices}});
+  $ReverseOtherID = join('-', reverse(@{$Other->{Vertices}}));
+
+  return ($ThisID =~ /^($OtherID|$ReverseOtherID)$/) ? 1 : 0;
+}
+
+# Return a string containing vertices in the path...
+sub StringifyPath {
+  my($This) = @_;
+  my($PathString);
+
+  $PathString = "Path: " . join('-', @{$This->{Vertices}});
+
+  return $PathString;
+}
+
+# Is it a path object?
+sub _IsPath {
+  my($Object) = @_;
+
+  return (Scalar::Util::blessed($Object) && $Object->isa($ClassName)) ? 1 : 0;
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Path - Path class
+
+=head1 SYNOPSIS
+
+use Graph::Path;
+
+use Graph::Path qw(:all);
+
+=head1 DESCRIPTION
+
+B<Path> class provides the following methods:
+
+new, AddVertex, AddVertices, Copy, GetCommonVertices, GetEdges, GetEndVertex,
+GetLength, GetStartVertex, GetTerminalVertices, GetVertex, GetVertices, IsCycle,
+IsIndependentCyclicPath, IsIndependentPath, IsPath, Join, JoinAtVertex, PopVertex,
+PushVertex, PushVertices, Reverse, ShiftVertex, StringifyPath, UnshiftVertex,
+UnshiftVertices
+
+Path is a sequential list of vertices with an edge between two successive vertices. The path
+becomes a cycle when start vertex and end vertex are the same.
+
+The following operators are overloaded:
+
+    "" == eq
+
+=head2 METHODS
+
+=over 4
+
+=item B<new>
+
+    $NewPath = new Path();
+    $NewPath = new Path(@VertexIDs);
+
+Using specified I<VertexIDs>, B<new> method creates a new B<Path> object and returns
+newly created B<Path> object.
+
+=item B<AddVertex>
+
+    $Path->AddVertex($VertexID);
+
+Adds I<VertexID> to I<Path> and returns I<Path>.
+
+=item B<AddVertices>
+
+    $Path->AddVertices(@VertexIDs);
+
+Adds vertices using I<VertexIDs> to I<Path> and returns I<Graph>.
+
+=item B<Copy>
+
+    $Return = $Path->Copy();
+
+Copies I<Path> and its associated data using B<Storable::dclone> and returns a new
+B<Path> object.
+
+=item B<GetCommonVertices>
+
+    @CommonVertices = $Path->GetCommonVertices($OtherPath);
+    $NumOfCommonVertices = $Path->GetCommonVertices($OtherPath);
+
+Returns an array containing common vertex IDs between two paths. In scalar context, number
+of common vertices is returned.
+
+=item B<GetEdges>
+
+    @EdgesVertexIDs = $Path->GetEdges();
+    $NumOfEdges = $Path->GetEdges();
+
+Returns an array containg successive paris of vertex IDs corresponding to all edges in I<Path>.
+In scalar context, the number of edges is returned.
+
+=item B<GetEndVertex>
+
+    $VertexID = $Path->GetEndVertex();
+
+Returns B<VertexID> of end vertex in I<Path>.
+
+=item B<GetLength>
+
+    $Length = $Path->GetLength();
+
+Returns B<Length> of I<Path> corresponding to number of vertices in I<Path>.
+
+=item B<GetStartVertex>
+
+    $VertexID = $Path->GetStartVertex();
+
+Returns B<VertexID> of start vertex in I<Path>.
+
+=item B<GetTerminalVertices>
+
+    ($StartVertexID, $EndVertexID) = $Path->GetTerminalVertices();
+
+Returns vertex IDs of start and end vertices in I<Path>.
+
+=item B<GetVertex>
+
+    $VertexID = $Path->GetVertex($Index);
+
+Returns specific vertex ID from I<Path> corresponding to I<Index> with indicies starting from 0.
+
+=item B<GetVertices>
+
+    @Vertices = $Path->GetVertices();
+    $NumOfVertices = $Path->GetVertices();
+
+Returns an array containing all vertex IDs in I<Path>. In scalar context, number of vertices
+is returned.
+
+=item B<IsCycle>
+
+    $Status = $Path->IsCycle();
+
+Returns 1 or 0 based on whether I<Path> is a B<CyclicPath> which has the same start and
+end vertex IDs.
+
+=item B<IsIndependentCyclicPath>
+
+    $Status = $Path->IsIndependentCyclicPath();
+
+Returns 1 or 0 based on whether I<Path> is an independent B<CyclicPath>. For a I<Path> to be
+an independent cyclic path, it must be a cyclic path and have unique vertices.
+
+=item B<IsIndependentPath>
+
+    $Status = $Path->IsIndependentPath();
+
+Returns 1 or 0 based on whether I<Path> is an independent B<Path>. For a I<Path> to be
+an independent path, it must have unique vertices.
+
+=item B<IsPath>
+
+    $Status = Graph::Path::IsPath();
+
+Returns 1 or 0 based on whether I<Object> is a B<Path> object
+
+=item B<Join>
+
+    $NewPath = $Path->Join($OtherPath);
+    $NewPath = $Path->Join(@VertexIDs);
+
+Joins existing I<Path> with a new path specified as a I<OtherPath> object or an array of I<VertexIDs>
+and returns I<NewPath>.
+
+In order to successfully join two paths, terminal vertices must have a common vertex. Based on the
+common terminal vertex found, additional path vertices are added to the current I<Path> in one of
+the following four ways:
+
+    . EndVertex = NewStartVertex: New path at end of current path with
+      same vertices order
+
+    . EndVertex = NewEndVertex: New path at end of current path with
+      reversed vertices order
+
+    . StartVertex = NewEndVertex: New path at front of current path
+      with same vertices order
+
+    . StartVertex = NewStartVertex: New path at front of current path
+      with reversed vertices order
+
+=item B<JoinAtVertex>
+
+    $NewPath = $Path->JoinAtVertex($OtherPath, $CenterVertexID);
+
+Joins existing I<Path> with I<OtherPath> at a specified I<CeterVertexID> and returns a I<NewPath>.
+
+=item B<PopVertex>
+
+    $Path->PopVertex();
+
+Removes end vertex from I<Path> and returns I<Path>.
+
+=item B<PushVertex>
+
+    $Path->PushVertex($VertexID);
+
+Adds I<VertexID> to I<Path> after end vertex and returns I<Path>.
+
+=item B<PushVertices>
+
+    $Path->PushVertices(@VertexIDs);
+
+Adds I<VertexIDs> to I<Path> after end vertex and returns I<Path>.
+
+=item B<Reverse>
+
+    $Path->Reverse();
+
+Reverses order of vertices in I<Path> and returns I<Path>.
+
+=item B<ShiftVertex>
+
+    $Path->ShiftVertex();
+
+Removes start vertex from I<Path> and returns I<Path>.
+
+=item B<StringifyPath>
+
+    $String = $Path->StringifyPath();
+
+Returns a string containing information about I<Path> object.
+
+=item B<UnshiftVertex>
+
+    $Path->UnshiftVertex($VertexID);
+
+Adds I<VertexID> to I<Path> before start vertex and returns I<Path>.
+
+=item B<UnshiftVertices>
+
+    $Path->UnshiftVertices(@VertexIDs);
+
+Adds I<VertexIDs> to I<Path> before start vertex and returns I<Path>.
+
+=back
+
+=head1 AUTHOR
+
+Manish Sud <msud@san.rr.com>
+
+=head1 SEE ALSO
+
+PathGraph.pm, PathsTraversal.pm
+
+=head1 COPYRIGHT
+
+Copyright (C) 2015 Manish Sud. All rights reserved.
+
+This file is part of MayaChemTools.
+
+MayaChemTools is free software; you can redistribute it and/or modify it under
+the terms of the GNU Lesser General Public License as published by the Free
+Software Foundation; either version 3 of the License, or (at your option)
+any later version.
+
+=cut