// L-40 MCS 360 Fri 1 Dec 2017 : shortest_paths.cpp

// Runs Dijkstra's algorithm on a randomly generated graph
// to compute the shortest paths from vertex 0.

#include <iostream>
#include <iomanip>
#include <cstdlib>
#include <vector>
#include <limits>
#include <set>

using namespace std;

void allocate_double_matrix ( vector< vector<double> >& mat, size_t dim );
/*
   Allocates space in mat for an adjacency matrix of dimension dim. */

int loaded_coin ( double p );
/*
   A fair coin will turn out as many 0 as 1 if p = 0.5,
   as p is the probability of 0. */

void random_directed_weighted_graph
 ( vector< vector<double> >& mat, double prb );
/*
   Fills up the adjacency matrix mat for a random directed graph.
   The value of prb is the probability that there is an edge.
   The weights are random doubles between 0.1 and 9.9. */

void write_double_matrix ( vector< vector<double> >& mat );
/*
   Writes the matrix mat to screen. */

void write_data ( vector<double>& dis, vector<size_t>& prv );
/*
   Writes the distances dis and predecessors prv. */

void write_sets ( set<size_t>& done, set<size_t>& todo );
/*
   Writes the sets done and todo. */

size_t idx_min_dis ( set<size_t> s, vector<double> d );
/*
   Returns the index of the element in s which has the smallest
   value for its distance in the corresponding vector d.
   Returns d.size() if there is no index with distance < oo. */

void initialize
 ( vector< vector<double> >& mat, size_t start,
   vector<double>& dis, vector<size_t>& prv,
   set<size_t>& done, set<size_t>& todo, bool verbose );
/*
   Given the adjacency matrix mat for a directed weighted graph
   and a vertex start, initializes the vectors dis and prv,
   and initializes the sets done and todo.
   If verbose, then dis and prv are printed. */

void shortest_paths
 ( vector< vector<double> >& mat, size_t start,
   vector<double>& dis, vector<size_t>& prv, bool verbose );
/*
   Given the adjacency matrix mat for a directed weighted graph
   and a vertex start, returns two vectors dis and prv.
   On return, dis[k] the shortest distance of vertex start to vertex k,
   and prv[k] is the previous vertex in the shortest path from start to k.
   If verbose, then intermediate values of dis and prv are printed. */

void write_path
 ( vector< vector<double> >& mat, size_t start, size_t dest,
   vector<double>& dis, vector<size_t>& prv, size_t vtx, double sumdis );
/*
   Given the predecessors in prv, the vertices start and dest,
   writes the path from dest back to start.
   The distance of the path is computed and printed with dis[dest].  */

void write_results
 ( vector< vector<double> >& mat, size_t start,
   vector<double>& dis, vector<size_t>& prv );
/*
   Given the adjacency matrix mat for a directed weighted graph
   and a vertex start, writes the results of the shortest_paths:
   dis[k] the shortest distance of vertex start to vertex k, and
   prv[k] is the previous vertex in the shortest path from start to k. */

int main ( void )
{
   size_t seed = time(NULL);
   cout << "The seed : " << seed << endl;
   srand(seed); // srand(time(NULL));

   cout << "Give the number of vertices : ";
   size_t n; cin >> n;

   cout << "Give a probability in [0.0, 1.0] : ";
   double p; cin >> p;

   vector< vector<double> > mat;
   allocate_double_matrix(mat, n);
   random_directed_weighted_graph(mat, p);
   write_double_matrix(mat);

   cout << "Give the vertex start : ";
   size_t s; cin >> s; 

   vector<double> distances;
   for(size_t i=0; i<mat.size(); i++) distances.push_back(0.0);
   vector<size_t> predecessors;
   for(size_t i=0; i<mat.size(); i++) predecessors.push_back(0);

   shortest_paths(mat, s, distances, predecessors, true);

   write_results(mat, s, distances, predecessors);

   return 0;
}

void allocate_double_matrix ( vector< vector<double> >& mat, size_t dim )
{
   for(size_t i=0; i<dim; i++)
   {
      vector<double> row;

      for(size_t j=0; j<dim; j++) row.push_back(0.0);

      mat.push_back(row);
   }
}

int loaded_coin ( double p )
{
   double r = ((double) rand())/RAND_MAX;

   return (r <= p ? 0 : 1);
}

void random_directed_weighted_graph
 ( vector< vector<double> >& mat, double prb )
{
   const size_t dim = mat.size();

   for(size_t i=0; i<dim; i++)
      for(size_t j=0; j<dim; j++)  // place edges at random
      {
         size_t rnd = loaded_coin(prb);  // edge or not
         if(rnd == 0)
            mat[i][j] = 0.0;       // no edge => zero weight
         else
         {
            rnd = 1 + rand() % 99; // random weight 
            mat[i][j] = ((double) rnd)/10.0; // in [0.1, 9.9]
         }
      }
}

void write_double_matrix ( vector< vector<double> >& mat )
{
   const size_t dim = mat.size();

   cout << fixed << setprecision(1);
   
   for(size_t i=0; i<dim; i++)
   {
      for(size_t j=0; j<dim; j++)
         cout << " " << mat[i][j];
      cout << endl;
   }  
}

void write_data ( vector<double>& dis, vector<size_t>& prv )
{
   cout << "The distances :" << endl;
   for(size_t i=0; i<dis.size(); i++)
      cout << " " << dis[i];
   cout << endl;

   cout << "The predecessors :" << endl;
   for(size_t i=0; i<prv.size(); i++)
      cout << " " << prv[i];
   cout << endl;
}

void write_sets ( set<size_t>& done, set<size_t>& todo )
{
   cout << "The set done :";
   for(set<size_t>::const_iterator i=done.begin(); i != done.end(); i++)
      cout << " " << *i;
   cout << endl;

   cout << "The set todo :";
   for(set<size_t>::const_iterator i=todo.begin(); i != todo.end(); i++)
      cout << " " << *i;
   cout << endl;
}

size_t idx_min_dis ( set<size_t> s, vector<double> d )
{
   size_t result = d.size();
   const double oo = numeric_limits<double>::infinity();
   double mindis = oo;

   for(set<size_t>::const_iterator i=s.begin(); i != s.end(); i++)
   {
      if(d[*i] < mindis)
      {
         mindis = d[*i];
         result = *i;
      }
   }

   return result;
}

void initialize
 ( vector< vector<double> >& mat, size_t start,
   vector<double>& dis, vector<size_t>& prv,
   set<size_t>& done, set<size_t>& todo, bool verbose )
{
   const size_t dim = mat.size();
   const double oo = numeric_limits<double>::infinity();

   for(size_t i=0; i<dim; i++)
   {
      dis[i] = (mat[start][i] == 0.0 ? oo : mat[start][i]);
      prv[i] = start;
   }
   done.insert(start);
   for(size_t i=0; i<dim; i++)
      if(i != start) todo.insert(i);

   if(verbose)
   {
      write_data(dis, prv);
      write_sets(done, todo);
   }
}

void shortest_paths
 ( vector< vector<double> >& mat, size_t start,
   vector<double>& dis, vector<size_t>& prv, bool verbose )
{
   set<size_t> done; // vertices already processed
   set<size_t> todo; // vertices left to process
   initialize(mat, start, dis, prv, done, todo, verbose);

   while(not todo.empty())
   {
      size_t vtx = idx_min_dis(todo, dis); // closest vertex
      if(vtx == dis.size())
      {
         if(verbose) cout << "No distance < oo." << endl;
         break;
      }
      done.insert(vtx); todo.erase(vtx);   // remove from todo
      if(verbose) write_sets(done,todo);

      for(set<size_t>::const_iterator i=todo.begin(); i!=todo.end(); i++)
         if(mat[vtx][*i] != 0.0)
            if(dis[vtx] + mat[vtx][*i] < dis[*i])
            {
               dis[*i] = dis[vtx] + mat[vtx][*i];
               prv[*i] = vtx;
            }
      if(verbose) write_data(dis, prv);
   }
}

void write_path
 ( vector< vector<double> >& mat, size_t start, size_t dest,
   vector<double>& dis, vector<size_t>& prv, size_t vtx, double sumdis )
{
   double newsum = sumdis + mat[prv[vtx]][vtx];

   cout << " <- " << prv[vtx];
   if(prv[vtx] == start)
      cout << " : " << newsum << " : " <<dis[dest] << endl;
   else
      write_path(mat, start, dest, dis, prv, prv[vtx], newsum);
}

void write_results
 ( vector< vector<double> >& mat, size_t start,
   vector<double>& dis, vector<size_t>& prv )
{
   cout << "The adjacency matrix :" << endl;
   write_double_matrix(mat);
   cout << "The shortest paths from " << start << " : " << endl;
   for(size_t i=0; i<prv.size(); i++)
   {
      cout << i;
      write_path(mat, start, i, dis, prv, i, 0.0);
   }
}
