Bits of Learning

Learning sometimes happens in big jumps, but mostly in little tiny steps. I share my baby steps of learning here, mostly on topics around programming, programming languages, software engineering, and computing in general. But occasionally, even on other disciplines of engineering or even science. I mostly learn through examples and doing. And this place is a logbook of my experiences in learning something. You may find several things interesting here: little cute snippets of (hopefully useful) code, a bit of backing theory, and a lot of gyan on how learning can be so much fun.
Showing posts with label graph. Show all posts
Showing posts with label graph. Show all posts

Wednesday, March 03, 2021

Automating Your Build with Makefiles

I know I am getting into a well-known topic. What more, make is a pretty old technology. We have so many other cooler build automation technologies out there. IDEs like Eclipse and Visual Studio make it unnecessary to write your old build system in the first place.

After the above negative marketing, let me tell you why then I am writing this post: because understanding how dependencies work between the various modules of your software is fundamentally connected to your design thinking. In fact, understanding how make's algorithm works at a high level is worth the effort. I present a prototype implementation of the same below, all of ~30 lines of OCaml code.

A Running Example

(Adapted from a illustrative project by my student Anirudh C.)


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
(* arith.ml -- begin *)
let inc x = x + 1
let dec x = x - 1

let add x y =
  let rec loop x y =
    if y = 0 then x else loop (inc x) (dec y)
  in loop x y

let sub x y =
  let rec loop x y =
    if y = 0 then x else loop (dec x) (dec y)
  in loop x y
(* arith.ml -- end *)  
  
(* circle.ml -- begin*)
let pi = 3.141
let area r = pi *. r *. r
let circumference r = 2. *. pi *. r
(* circle.ml - end *)

(* main.ml -- begin *)
let main () =
  print_endline (string_of_int (Arith.add 1 2));
  print_endline (string_of_int (Arith.sub 3 2));

  print_endline (string_of_float (Circle.area 2.))

let _ = main ()
(* main.ml -- end *)

In the code shown above, we have three modules, arith (arith.ml, arith.mli), circle (circle.ml, circle.mli) and main (main.ml, main.mli). main uses both arith and circle which are self contained modules. The OCaml implementation of the above idea, therefore, has the following additional files:

arith.cmi: The object code for the interface of arith module

arith.cmo: The object code for the implementation of arith module

circle.cmi: The object code for the interface of circle module

circle.cmo: The object code for the implementation of circle module

main.cmi: The object code for the interface of main module

main.cmo: The object code for the implementation of main module

app: The final executable.

 Here are the listings of the interface files:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
(* arith.mli -- begin *)
val add : int -> int -> int
val sub : int -> int -> int
(* arith.mli -- end *)  
  
(* circle.mli -- begin*)
val area : float -> float
val circumference : float -> float
(* circle.mli - end *)

(* main.mli -- begin *)
val main: unit -> unit
(* main.mli -- end *)

 

The dependencies between the various files is shown by the following directed acyclic graph:


As you can see, the dependencies look a bit complicated, if not a lot. Part of why it's this way has to do with the particular programming language that you are using. For example, OCaml has this idea of object code being generated from the interface files by compiling the .mli file. However, the most important aspect of this structure is agnostic to the programming language. That aspect of the dependencies is its directed acyclic nature. In the dependency graph above, you will find no cycles or loops.

What impact do the above dependency relations have on build automation process. Here is the exhaustive sequence of build steps:

1
2
3
4
5
6
7
ocamlc -c arith.mli                         # builds arith.cmi
ocamlc -c arith.ml                          # builds arith.cmo
ocamlc -c circle.mli                        # builds circle.cmi
ocamlc -c circle.ml                         # builds circle.cmo
ocamlc -c main.mli                          # builds main.cmi
ocamlc -c main.ml                           # builds main.cmo
ocamlc -o app arith.cmo circle.cmo main.cmo # builds app


The first influence is the following: The dependee must always be compiled after all its dependencies have been compiled. The sequence in which the above steps are written respects this order.

The second one is an opportunity of optimisation: When you are building the application, you compile only that part of the application which is depends on any part which has been modified since the last build. For example, if we make a modification in the circle.ml file, which parts of the build process need to be repeated? Looking at the dependency graph we can say that its circle.cmo, and app, because these are only two nodes in the graph from which you can reach circle.ml by following the directed edges of the dependence graph.

 The make Algorithm

The make algorithm allows us to implement the above optimisation. We specify the dependency graph in a makefile. Here's the Makefile for the above application:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
app : arith.cmo circle.cmo main.cmo
	ocamlc -o app arith.cmo circle.cmo main.cmo

arith.cmo: arith.ml arith.cmi
	ocamlc -c arith.ml

circle.cmo: circle.ml circle.cmi
	ocamlc -c circle.ml

arith.cmi : arith.mli
	ocamlc -c arith.mli

circle.cmi : circle.mli
	ocamlc -c circle.mli

main.cmo : main.ml main.cmi arith.cmi circle.cmi
	ocamlc -c main.ml

main.cmi : main.mli
	ocamlc -c main.mli



 

 

 

make algorithm takes this graph as an input, studies the modification timestamps of all the targets and does just what is necessary (and useful) in the build process of the application. What's the rule to be followed here? Whenever any of the dependencies, say d, of a dependee/target file f was modified after f was modified, f needs to be rebuilt. However, one more bit. The timestamp of f and d is checked only after this rule is applied on d. And recursively. While an untrained human mind may find this a bit mind-bending, it's really a very very simple algorithm.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
function process_node(n)
  if(n.deps != {}) then
    process_node(n') for all n' in n.deps
    maxdepts = max(timestamps(n.deps))
    if(timestamp(n) < maxdepts) then
      execute(n.action)
      update_timestamp(n, now)
    endif
  endif
end


The algorithm above does a depth first traversal starting with the target which we are building. After having processed all the dependencies, it compares the timestamp of the current target with those of all its dependencies. If any one of them is more recently modified than the target, the target is rebuilt by executing its build command.

To round up the article, here's the OCaml implementation of the above algorithm. Even though the code has a good 170 LOC, note that the core algorithm is just 33 lines (L42 to L72). Rest of it is all plumbing and testing code.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
type node = {
  name : bytes;
  dependencies : node list;
  action : bytes;
}

type timestamp = {
  n : node;
  t : int;
}

let string_of_timestamp ts =
  "\t(" ^ ts.n.name ^ ", " ^ (string_of_int ts.t) ^ ")"

let string_of_timestamps tslist =
  "[" ^
    (let strtss = List.map string_of_timestamp tslist in
    (List.fold_left (fun x y -> x ^ y ^ "\n") "" strtss)) ^
  "]" 

exception My_exception of string

let rec get_timestamp timestamps n' =
  match timestamps with
    [] -> raise (My_exception n'.name)
  | { n; t } :: tl ->
    if n = n' then t
    else (get_timestamp tl n')

let rec update_timestamp timestamps n' t' =
  match timestamps with
    [] -> []
  | { n = n; t = t } :: tl ->
    if n = n' then { n = n'; t = t'} :: tl
    else { n ; t } :: (update_timestamp tl n' t')

let rec max = function
    [] -> raise Not_found
  | [h] -> h
  | h :: t -> let maxt = max t in if h > maxt then h else maxt

let rec process_node n ts =
  let myt = get_timestamp ts n in
  if (List.length n.dependencies <> 0) then
    let depts = process_dependencies n.dependencies ts in
    let maxts = max (List.map (get_timestamp depts) n.dependencies) in
    if myt < maxts then
    begin
      print_endline ("executing " ^ n.action);
      let ts' = update_timestamp depts n (maxts + 1) in
      begin
        print_endline (string_of_timestamps ts');
        ts'
      end
    end
    else
    begin
      print_endline ("Nothing to be done for " ^ n.name);
      ts
    end
  else
    begin
      print_endline ("Nothing to be done for " ^ n.name);
      ts
    end

and process_dependencies deps ts =
  let rec loop deps ts =
    match deps with
      [] -> ts
    | d :: deps' ->
        let ts' = process_node d ts in loop deps' ts'
  in
  loop deps ts

let arith_mli = {
  name = "arith.mli";
  dependencies = [];
  action = "";
}
let arith_cmi = {
  name = "arith.cmi";
  dependencies = [arith_mli];
  action = "ocamlc -c arith.mli"
}
let arith_ml = {
  name = "arith.ml";
  dependencies = [];
  action = "";
}
let arith_cmo = {
  name = "arith.cmo";
  dependencies = [arith_ml; arith_cmi];
  action = "ocamlc -c arith.ml";
}
let circle_mli = {
  name = "circle.mli";
  dependencies = [];
  action = "";
}
let circle_cmi = {
  name = "circle.cmi";
  dependencies = [circle_mli];
  action = "ocamlc -c circle.mli"
}
let circle_ml = {
  name = "circle.ml";
  dependencies = [];
  action = "";
}
let circle_cmo = {
  name = "circle.cmo";
  dependencies = [circle_ml; circle_cmi];
  action = "ocamlc -c circle.ml";
}
let main_mli = {
  name = "main.mli";
  dependencies = [];
  action = "";
}
let main_cmi = {
  name = "main.cmi";
  dependencies = [main_mli];
  action = "ocamlc -c main.mli"
}
let main_ml = {
  name = "main.ml";
  dependencies = [];
  action = "";
}
let main_cmo = {
  name = "main.cmo";
  dependencies = [main_ml; main_cmi; circle_cmi; arith_cmi];
  action = "ocamlc -c main.ml";
}
let app = {
  name = "app";
  dependencies = [arith_cmo; circle_cmo; main_cmo];
  action = "ocamlc -o app arith.cmo circle.cmo main.cmo";
}
 
let ts = [
  {n = arith_mli;  t = 1};
  {n = arith_cmi;  t = 0};
  {n = arith_ml;   t = 0};
  {n = arith_cmo;  t = 0};
  {n = circle_mli; t = 0};
  {n = circle_cmi; t = 0};
  {n = circle_ml;  t = 0};
  {n = circle_cmo; t = 0};
  {n = main_mli;   t = 0};
  {n = main_cmi;   t = 0};
  {n = main_ml;    t = 0};
  {n = main_cmo;   t = 0};
  {n = app;        t = 0};
]

let t1 () =
  process_node app ts

let t2 () =
  (update_timestamp ts main_cmo 10) |> string_of_timestamps |> print_endline 

let t3 () =
  (get_timestamp ts arith_mli) |> string_of_int |> print_endline;
  (get_timestamp ts circle_mli) |> string_of_int |> print_endline

let _ = t2 ()
let _ = t1 ()
let _ = t3 ()


Hope you found the above useful and interesting!

Tuesday, September 13, 2016

APIs with Functions

This post introduces the concept of application programming interfaces (APIs) and what they are good for. As an example, we continue to use the Hisaab program.

As we have seen, functions are a very powerful abstraction mechanism to wrap around pieces of computation which can then be used and re-used as needed. This leads to a code which is more compact, readable, modifiable and flexible.

In Hisaab, the implementation we first created uses a particular representation of the graph data-structure called the edge list. For example, the following graph will be represented by the code right below that:



[
  ("A", 15, "F"),
  ("A", 20, "B"),
  ("B", 10, "D"),
  ("B", 10, "C"),
  ("D", 30, "C"),
  ("C", 30, "G"),
  ("G", 25, "E"),
  ("F", 100, "E"),
  ("E", 150, "D"),
  ("G", 90, "D")
]
The code of the algorithm is reproduced below:



 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
def reroute((n1, w, n2), n):
  if(n1 == n): return [(n2, -w, n1)]
  elif(n1 != n) and (n2 != n): return [(n1, w, n), (n2, -w, n)]
  else: return [(n1, w, n2)]

def starify_graph(g, n):
  new_g = []
  for e in g:
    new_edge = reroute(e, n)
    new_g.extend(new_edge)
  return new_g

# assumption: g is not a multigraph.
def merge((n1, w, n2), g):
  for i in range(len(g)):
    (m1, w1, m2) = g[i]
    if(m1 == n1 and m2 == n2):
      g[i] = (n1, w + w1, n2)
      return g
  g.append((n1, w, n2))
  return g

def graph_of_mgraph(mg):
  g = []
  for e in mg:
    g = merge(e, g)
  return g


The above code depends on the way we have implemented the graph data-structure. There are several examples in the above code which demonstrate the fact that the above code has been written with the knowledge about the internal implementation of the graph data-structure.

  • Line 1, 14: assumes that edges are triples.
  • Line 8, 10, 16, 18, 20, 24: graph is assumed to be an edge list.

The disadvantage with the above implementation of the algorithm is that it is strictly dependent on the way the graph data-structure is implemented. If the implementation of graph changes, the algorithm would have to be pretty much completely re-implemented.

Let's see how the above graph represented as an adjacency list would look like:

{
  'A': [('F', 15), ('B', 20)],
  'C': [('G', 30)],
  'B': [('D', 10), ('C', 10)],
  'E': [('D', 150)],
  'D': [('C', 30)],
  'G': [('E', 25), ('D', 90)],
  'F': [('E', 100)]
}

Below, we should the implementation of the algorithm when the graph is implemented as an adjacency list.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
def starify_graph(g, n):
  new_g = {} # instantiating a dictionary
  for n1 in g: #initialising the empty graph
    new_g[n1] = []
  for n1 in g:
    # invert and add every node starting with n.
    if(n1 == n):
      for (n2, w) in g[n1]:
        add_edge(new_g[n2], (n1, -w))
    else:
      for (n2, w) in g[n1]:
        # add every node ending at n.
        if(n2 == n):
          add_edge(new_g[n1], (n2, w))
        # reroute all other nodes.
        else:
          add_edge(new_g[n1], (n, w))
          add_edge(new_g[n2], (n, -w))
  return new_g

As is evident, this implementation is significantly different from the previous implementation. Again, there are several evidences in this code to show that it strictly assumes an adjacency list implementation of the graph data-structure.
Line 2: Graph is a dictionary.
Line 4: The elements of the graph dictionary are lists.
Line 8, 9, 11: The elements of the graph dictionary are pairs.

Modularity

Can we implement the above hisaab algorithm in a way so that it is independent of the internal details of the graph data-structure?

If we could do so, there would be the following advantages:
  • The hisaab algorithm would work fine irrespective of which implementation of graph we use.
  • The graph data-structure implementation can be modified subsequently without a risk of breaking the hisaab code.

The answer to the above question is Yes. By defining a set of functions which define a uniform way by which the rest of the code interacts with the graph code, we can ensure that there is separation between the view of the graph that the rest of the code sees, and its internal implementation. We do so, using a group of functions as follows:
  1. empty_graph: Returns a new empty graph with no nodes or edges
  2. make_graph: Returns a new graph having the edges which are provided in a list
  3. get_edges: Returns the list of edges in the graph
  4. add_edge: Adds an edge to a graph
The implementation of the above functions is specific to the specific implementation of the graph, but how they are used is not. Hence, the code which uses a graph only through the above functions is also independent of the specific implementation of the graph data-structure. The above set of functions can be termed as an application programming interface (API). This is because, this set defines the way one part of the program (called the client) interacts with another part of the program (called the server). Further functions like empty_graph and make_graph are called constructors (because they create new instances of the data-structure), get_edges are called extractors (because they reveal information about the data-structure without modifying it) and add_edge are called modifiers (because they modify the underlying data-structure). One way to look at the entire scenario would be as follows:

The client code (or user code) makes use of the facilities provided by the server code (or back-end code). In hisaab, the algorithm (implemented through the functions reroute and starify_graph) can be considered as the client code, while the graph data-structure can be viewed as the server code. As shown above, the API consisting of the four functions listed above form the interface through which the client talks to the server. Note that it is possible to freely change which of the two implementations the interface uses as the server without affecting the client. This gives us the flexibility of independently implementing the client without concerning ourselves about the internal details of the server.



 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
def reroute(e, n):
  n1 = get_start_node(e)
  w  = get_weight(e)
  n2 = get_end_node(e)
  if(get_start_node(e) == n):
    return [make_edge(n1 = n2, w = -w, n2 = n1)]
  elif(n1 != n) and (n2 != n):
    return [make_edge(n1 = n1, w = w, n2 = n), make_edge(n1 = n2, w = -w, n2 = n)]
  else:
    return [e]

def starify_graph(g, n):
  new_g = empty_graph()
  edges = get_edges(g)
  for e in edges:
    new_edges = reroute(e, n)
    for e in new_edges:
      add_edge(g = new_g, e = e)
  return new_g

The above code shows the hisaab algorithm re-implemented using the above API. The best way to get convinced this code is independent of any of the internal implementation details of graph data-structure is to look for a dependence. You will not find one!

The API allows us to implement hisaab as a re-usable algorithm independent of the implementation of the graph.

Large Scale Software Development and Software Design

In large scale software development scenario, software systems are developed by teams of multiple software developers. It would be too inefficient to develop each part of the system sequentially. It's important to ensure that all teams are kept busy throughout the project duration. All sub-teams in a project work in parallel to ensure an early completion of the project.

The way the above is achieved is by dividing the work into several modules which interact with each other to create the complete system. This process is called software design. The division is worked out in a way that there is comparable distribution of work between the various modules. More importantly, each module defines an interface (or API) through which the rest of the system interacts with that module. A good design will ensure that the interfaces are enough to allow powerful interaction between modules but doesn't expose unnecessary details of a module to the rest of the system. While the internal implementation of a module is likely to change rapidly over the execution of a project, the module interfaces must be decided early and frozen. This is important to ensure that all other parts of the system can proceed assuming this interface about the given module. Hence, every sub-team, working on an individual module, can proceed in parallel.

Prototypes and Improvisations

As mentioned above, API enable independent development of interacting modules. Suppose, one team is developing the client C, and another is developing the server S. A typical way this piece of software would be developed in practice is as follows: An API for S is defined first. As C is dependent on S, a quick-and-dirty implement of S -- say S1 -- is created and handed over to the client team so that C's development can proceed. While C's development proceeds, the server team continues working on a better-engineered version of S -- say S2 -- in parallel. S2 would follow the same API of S, however may score better than S1 in certain parameters like performance, security etc. By the time C's development gets over, S2 is also ready. The version of the product which gets shipped to the customer uses C as client S2 as the server.

Code:

hisaab1.py: Implements hisaab using a edge-list implementation
hisaab2.py: Implements hisaab using an adjacency-list implementation
hisaab3.py: Implements hisaab as a re-usable code by using a graph API

Friday, September 02, 2016

Hisaab -- A Program to Simplify Transactions among Friends

It's always fun when your technical knowledge comes in handy while to trying to solve some day-to-day problem. you get a kick in being able to describe a problem in its core mathematical essence, and in creating an algorithm to solve the problem. The size of the kick is independent of the size and complexity of the problem.

Follows a cute example of the above.

Problem Description

Often, a group of people may transact money between each other. A situation may arise when each person may have taken some money from someone else, thus owing the other person that much amount. To settle the accounts, there might be needed a very large number of transactions needed, requiring everyone to meet everyone else.

Solution Approach

good way to simplify the settlement process would be to make one of the people involved as the bank, or the star. Everyone makes a transaction with this person only, only the net amount she owes to anyone.

Mathematical Model






The original situation can be represented as a directed graph as shown above in the left side graph. Each node represents a person, and each directed edge indicates a transaction. For example, the edge with weight 15 between A and F means that A owes Rs. 15 to F.

The solution is shown in the right side graph above. Note that all edges now involve node D. Thus every node needs to make at most one transaction with node D, which is huge simplification of the process. The accounts will now be settled with only 5 transactions as opposed to 9 in the original graph. As an added benefit, nodes which need not transact at all also get identified. For example, node B gets isolated in the right side graph, meaning that it doesn't need to transact anything with node D.


Transforms

The solution is achieving through the following two simple graph transformations.

Rerouting



Suppose we wish to make node 1 the star node. This means that we want all transactions between any pair not involving node 1 should be routed through node 1, as shown above.

Multigraph to Graph




Implementation

Follows an implementation of the above idea in OCaml. We use a purely functional style (i.e. by using a completely side-effect free style of programming). If you remove the profuse amount in-line documentation, you will notice that the code is remarkably concise (just about 25 lines of code)!

Find an OCaml and Python implementation here.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
(*
The following program is useful to simplify the transactions amongst a group of people.
Problem Description: Often, a group of people may transact money between each other. A situation may arise when each person
may have taken some money from someone else, thus owing the other person that much amount. A
good way to simplify the settlement process would be to make one of the people involved as the
bank, or the star. Everyone makes a transaction with this person only, only the net amount she
owes to anyone.

User Instruction:
1. Open the file in a editor.
2. Edit the variable g to reflect what each person owes any other.
3. Save the file
4. Open the OCaml REPL
5. enter the command: #use "hisaab.ml";;

The answer will appear on the standard output.
*)

(*
  Description: Takes an edge e = (n1, w, n2) and a node n, and returns a list of edges, to
    apply starification transform on e.

  Signature: 'a * int * 'a -> 'a -> ('a * int * 'a) list

  Example:
  1)
# reroute (1, 10, 2) 1;;
- : (int * int * int) list = [(2, -10, 1)]
# reroute (2, 10, 1) 1;;
- : (int * int * int) list = [(2, 10, 1)]
# reroute (2, 10, 3) 1;;
- : (int * int * int) list = [(2, 10, 1); (3, -10, 1)]
*)
let reroute (n1, w, n2) n =
  if      n1 = n then [(n2, -w, n1)]
  else if n2 = n then [(n1, w, n2)]
  else                [(n1, w, n); (n2, -w, n)]

(*
  Description: Takes a list of lists and returns the corresponding list

  Signature: 'a list list -> 'a list

  Example:
# flatten_list [[1;2]; [3;4;5];[6]];;
- : int list = [1; 2; 3; 4; 5; 6]
*)
let rec flatten_list = function
    [] -> []
  | h :: t -> List.append h (flatten_list t)
 
(*
  Description: Takes a graph (could be multigraph) g and and a node n and returns
  and equivalent graph g' which is starred at n, and preserves the net input/output
  at each node of g.

  Signature: ('a * int * 'a) list -> 'a -> ('a * int * 'a) list
  Example:
# starify_graph [(1, 160, 2); (2, 440, 1); (1, 300, 3); (3, 160, 2)] 1;;
- : (int * int * int) list =
[(3, 160, 1); (2, -160, 1); (3, -300, 1); (2, 440, 1); (2, -160, 1)]
*)
let starify_graph g n =
  let list_of_lists = List.map (fun g -> reroute g n) g in
    flatten_list list_of_lists

(*
  Description: Takes an edge e = (n1, w, n2) and a graph g, and returns a 
  new graph g' such that g' is same as g except that if g contains an edge
  e' with same source and target nodes as e, then e and e' are merged by
  adding their weights.

  Signature: 'a * int * 'b -> ('a * int * 'b) list -> ('a * int * 'b) list

  Assumption: g is not a multi-graph.

  Example:
# let g2 = [(3, -140, 1); (2, 120, 1)];;
val g2 : (int * int * int) list = [(3, -140, 1); (2, 120, 1)]
# merge (3, 140, 1) g2;;
- : (int * int * int) list = [(3, 0, 1); (2, 120, 1)]
# merge (3, 140, 2) g2;; 
- : (int * int * int) list = [(3, -140, 1); (2, 120, 1); (3, 140, 2)]
# merge (3, 140, 2) [];;
- : (int * int * int) list = [(3, 140, 2)]
*)
let rec merge (n1, w, n2) = function
    [] -> [(n1, w, n2)]
  | (n1', w', n2') :: t ->
      if n1 = n1' && n2 = n2' then ((n1, w + w', n2) :: t)
      else (n1', w', n2') :: (merge (n1, w, n2) t)

(*
  Description: Takes a multigraph mg and returns its corresponding graph g by merging
    all edges between common pairs of nodes.

  Signature: ('a * int * 'b) list -> ('a * int * 'b) list

  Example:
# graph_of_mgraph [(1, 160, 2); (2, 300, 1); (2, 140, 1); (1, 300, 3); (3, 160, 2)];;
- : (int * int * int) list =
[(1, 160, 2); (2, 440, 1); (1, 300, 3); (3, 160, 2)]
*)  
let graph_of_mgraph mg =
  let rec iter g = function
    [] -> g
  | e :: t -> iter (merge e g) t
  in
  iter [] mg

(*
  Description: Turns the weight of every edge of a given graph to positive value by inverting the direction of the edge if its weight is negative.

  Signature: ('a * int * 'a) list -> ('a * int * 'a) list
  
  Example:
# abs_weight [("Taru", -140, "Shraddha"); ("Shilpi", 20, "Shraddha")];;
- : (string * int * string) list =
[("Shraddha", 140, "Taru"); ("Shilpi", 20, "Shraddha")]    
*)
let abs_weight g =
  List.map (
    fun (n1, w, n2) -> if w >= 0 then (n1, w, n2) else (n2, -w, n1)
  ) g

let remove_empty_edges g =
  List.filter (fun (_, w, _) -> w <> 0) g
(*
  Example graph (Provided by Shilpi)
  node 1 -> Shilpi
  node 2 -> Shraddha
  node 3 -> Taru

  (1, 160, 2) -> Shilpi owes Rs. 160 to Shraddha
*)
let g = [("Shilpi", 160, "Shraddha"); ("Shraddha", 300, "Shilpi"); ("Shraddha", 140, "Shilpi"); ("Shilpi", 300, "Taru"); ("Taru", 160, "Shraddha")]
let star_node = "Shraddha"
let _ = remove_empty_edges (
          abs_weight (
            graph_of_mgraph (
              starify_graph g star_node)))

let e1 = [
  ("r2", 10, "r3");
  ("r1", 20, "r2");
  ("r2", 10, "r4");
  ("r4", 30, "r3");
  ("r1", 15, "r6");
  ("r6", 100,"r5");
  ("r7", 25, "r5");
  ("r5", 150,"r4");
  ("r3", 30, "r7");
  ("r7", 90, "r4");
]

let star_node = "r4"
let _ = remove_empty_edges (
          abs_weight (
            graph_of_mgraph (
              starify_graph e1 star_node)))