TL;DR

Where we discuss about a generic model for the graph.

I find it very instructive to study #algorithms, and also to try to code them in the most generic way possible. Iâ€™m not sure why, actually: most of the times the implementation can be easily tailored to the problem I have, and sometimes this means just re-implementing the whole thingâ€¦ but thatâ€™s life.

Iâ€™ll gloss over a lot of pre-conditions here, assuming that you knowâ€¦ what a graph (in the graph theory sense) means.

# Representing the Graph - Generically

To remain general, we should aim for a representation that assumes nothing about how the algorithm will be used. Will it be objects? Simple hashes? Arrays? Anything else?

We will just do the following assumptions:

• a node is represented by a Perl scalar (this was easy);
• the nodes that can be reached from a node `X` are in a list that can be retrieved through a function.

This should give us a model that is generic enough to use, while still being simple enough to implement independently of the real graph representation.

# An example?

Letâ€™s make an example: suppose that the graph is represented like this:

• nodes are strings, representing places;
• edges are represented implicitly with a hash of arrays.

Something like this:

``````my %graph = (
airport => [qw< work >],
home    => [qw< work school >],
park    => [qw< school >],
school  => [qw< park home >],
work    => [qw< home airport >],
);
``````

This tells us that we can reach `work` from the `airport`, or that we can reach both `park` and `home` from `school`. This representation is good for a very generic graph, including directed graphs.

Our nodes in this case are the strings `airport`, `home`, `park`, `school`, and `work`. The list associated to each node allows representing the edges.

In our representation, the following function would allow us to represent these adjacencies through a function:

``````sub graph_dependencies_sub {
my %graph = @_;
return sub {
my (\$node) = @_;
return @{\$graph{\$node}};
}
}
``````

Now `\$graph_adjacencies` encapsulates our representation of the graph: when provided a node, it gives out the list of nodes that can be reached from it:

``````my @nodes_from_home = \$graph_adjacencies->('home');
``````

# Another example?

We might, of course, start from a different representation for the graph, e.g. based on objects:

• nodes are objects;
• edges are represented as a list of adjacent nodes that each node holds.
``````package Graph;

sub new {
my (\$package, \$name) = @_;
return bless {name => \$name, neighbors => {}}, \$package;
};

sub name {
my \$self = shift;
return \$self->{name};
}

sub neighbors {
my \$self = shift;
return @{\$self->{neighbors}};
}

my \$self = shift;
\$self->{neighbors}{\$_->name} = \$_ for @_;
return \$self;
}

package main;

my (\$airport, \$home, \$park, \$school, \$work) =
map { Graph->new(\$_) } qw< airport home park school work >;
``````

Itâ€™s basically the same graph as before, only represented differently. Again, itâ€™s easy to adapt to the generic representation, itâ€™s basically just `Graph::neighbors`:

``````my \$graph_adjacencies = \&Graph::neighbors;
``````

Of course if you intend to inherit from `Graph` this will break, so the more robust thing to do is to use a wrapper:

``````sub graph_dependencies_sub {
return sub {
my (\$node) = @_;
return \$node->neighbors;
};
}
``````

# Edge lists anyone?

This representation is sub-optimal when edges are represented as stand-alone elements, e.g.:

• nodes are strings;
• edges are represented as pairs of nodes in anonymous arrays `[\$from, \$to]`.

Something like this (for the same graph):

``````my @edges = (
[ 'airport', 'work' ],
[ 'home', 'work' ],
[ 'home', 'school' ],
[ 'park', 'school' ],
[ 'school', 'park' ],
[ 'school', 'home' ],
[ 'work', 'home' ],
[ 'work', 'airport' ],
);
``````

The adaptation in this case is a bitâ€¦ clunky:

``````my \$graph_adjacencies = sub {
my (\$node) = @_;
map { \$_->[1] } grep { \$_->[0] eq \$node } @edges;
}
``````

Every timeâ€¦ we are iterating through the whole of `@edges`. Of course we can do some pre-computing:

``````sub graph_adjacencies_sub {
my %graph;
for my \$edge (@_) {
my (\$from, \$to) = @\$edge;
\$graph{\$from} = \$to;
}
return sub {
my (\$node) = @_;
return @{\$graph{\$node}};
};
}
``````

â€¦ at the expense of requiring some more space.

But hey! I promised that the representation would be generic and easy to adapt to, not that it would solve every problem in the world!

# Summing Up

The representation that we introduced basically requires us to decide on what we mean by node and provide a function that computes the list of other nodes that can be reached from it. It allows us to represent whatever graph, and itâ€™s fairly easy to adapt toâ€¦ which we will leverage in the future discussing a few #algorithms about graphs.

Cheers!

Comments? Octodon, , GitHub, Reddit, or drop me a line!