2

I am using Map to implement pure functional DFS and BFS for graph.

here is my code:

module IntMap = Map.Make(struct type t = int let compare = compare end);;
module IntSet = Set.Make(struct type t = int let compare = compare end);;

type digraph = int list IntMap.t;;

exception CantAddEdge;;

let create v = 
  let rec fill i acc =
    if i < v then
      fill (i+1) (IntMap.add i [] acc)
    else 
      acc
  in 
  fill 0 IntMap.empty;;

let num_vertices g = IntMap.cardinal g;;

let add_edge u v g = 
  if IntMap.mem u g && IntMap.mem v g then 
    let add u v g =
      let l = IntMap.find u g in 
      if List.mem v l then g
      else IntMap.add u (v::l) g
    in 
    add u v (add v u g)
  else 
    raise CantAddEdge;;

let dfs_path u g =
  let rec dfs current visited path =
    let dfs_child current (visited, path) c =
      if not (IntSet.mem c visited) then
         dfs c (IntSet.add c visited) (IntMap.add c current path)
      else 
         (visited, path)
    in 
    List.fold_left (dfs_child current) (visited, path) (IntMap.find current g)
  in 
  let (v, p) = dfs u (IntSet.singleton u) IntMap.empty
  in 
  p;;

let bfs_path u g =
  let rec bfs current_list v p n =
    let bfs_current (v,p,n) current  =
      let bfs_child current (v, p, n) c = 
         if not (IntSet.mem c v) then begin
           print_int c;
           ((IntSet.add c v), (IntMap.add c current p), (c::n))
         end 
         else 
           (v, p, n)
      in 
      List.fold_left (bfs_child current) (v, p, n) (IntMap.find current g)
    in 
    let (v,p,n) = List.fold_left bfs_current (v,p,n) current_list
    in 
    if n = [] then p
    else bfs n v p []
  in  
  bfs [u] (IntSet.singleton u) IntMap.empty [];;

I know the code is quite long, but I really do wish for some suggestions:

  1. Is it worthy to really implement a pure functional set of graph algorithm? I do this because I am getting used to functional and hate imperative now.
  2. Is my implementation too complicated in some parts or all?
  3. Although I like functional, personally I think the implementation I make seems more complicated than the imperative array-everywhere version. Is my feeling correct?

Edit

Added Bipartite code

(* basically, we have two sets, one for red node and the other for black node*)
(* we keep marking color to nodes via DFS and different level of nodes go to coresponding color set*)
(* unless a node is meant to be one color but already in the set of the other color*)
type colorType = Red | Black;;
let dfs_bipartite u g =
  let rec dfs current color red black block  =
    if block then (red, black, block)
    else 
      let dfs_child current color (red, black, block) c =
    if block then (red, black, block)
    else 
      let c_red = IntSet.mem c red and c_black = IntSet.mem c black in
      if (not c_red) && (not c_black) then
        if color = Red then
          dfs c Black (IntSet.add c red) black false
        else
          dfs c Red red (IntSet.add c black) false
      else if (c_red && color = Black) || (c_black && color = Red) then (red, black, true)
      else (red, black, block)
      in 
      List.fold_left (dfs_child current color) (red, black, block) (IntMap.find current g)
  in 
  let (r, b, block) = dfs u Black (IntSet.singleton u) IntSet.empty false
  in 
  not block;;

Edit 2

DFS with list based path

let dfs_path u g =
  let rec dfs current visited path =
    let dfs_child (visited, path) c =
      if not (IntSet.mem c visited) then begin
    print_int c;
    dfs c (IntSet.add c visited) (c::path)
      end 
      else (visited, path)
    in 
    List.fold_left dfs_child (visited, path) (IntMap.find current g)
  in 
  let (v, p) = dfs u (IntSet.singleton u) [u]
  in 
  p;;
Jackson Tale
  • 25,428
  • 34
  • 149
  • 271

2 Answers2

3

I'm not sure what you mean by worthy. It's worthy to set yourself this task as a learning exercise. It's also worthy to use immutable data to solve actual real world graph problems. It doesn't seem to me that graph processing is an area of application where pure functional code costs more than one is generally willing to pay for the benefits.

You're representing a path as a map from each node to the next. This is nice because you can start up the path in the middle. But a list is a simpler and more natural representation of a path for a lot of applications. At any rate, yours is a pretty heavyweight representation and so it makes your code a little heavier than I would have expected. (BTW it was hard to figure this out--some comments would help.)

I don't personally think this code is more complicated than imperative could would be. I also think that arrays make a poor representation for graphs when viewed as linked structures. So I don't believe an "arrays everywhere" solution is what you want to compare against. I'd compare against a malloc()/struct based (a la C) or against an object-based solution, personally.

When representing graphs as adjacency matrices, I'd say the array representation is more competitive. If your graph changes size a lot, or if you want to access nodes by keys other than integers, maps still have many advantages.

Jeffrey Scofield
  • 65,646
  • 2
  • 72
  • 108
  • Thanks for your advice, I will add comments later. I just feel the thinking in functional dfs is quite different from in imperative dfs. Do you think the logic of my code can be further improved? as @Indicator indicates that I may combine several functions together? – Jackson Tale Apr 05 '13 at 15:39
  • for DFS, maybe it is ok to have path as list, but for BFS, it will cause confusion, right? – Jackson Tale Apr 05 '13 at 15:42
  • Any path is isomorphic to a list, unless I'm missing something. – Jeffrey Scofield Apr 05 '13 at 15:44
  • maybe my thought was to construct a thing so that we know who comes from whom. Your suggestion of using list will show really the path (order of travel) and don't care about the departure and arrival, right? – Jackson Tale Apr 05 '13 at 15:46
  • jeffrey, do you mind having a rough look at my `bipartite` code. I think it is really looking NOT that nice. I added comments this time. – Jackson Tale Apr 05 '13 at 15:50
  • A list shows one direction. Unless you invert your map, your map also shows only one direction. But you can also reverse a list to get the inverse direction. – Jeffrey Scofield Apr 05 '13 at 15:51
  • I mean for example, for graph {A->B, A->C, C->D}, if I use list as path for the traversal, then the list will be `A->B->C->D`. we can't tell, for instance, where we come from to C, right? as B is not C's parent actually – Jackson Tale Apr 05 '13 at 16:02
  • Unless I'm misunderstanding (definitely possible) your paths have the same property. – Jeffrey Scofield Apr 05 '13 at 16:03
  • also I added the new code using list based path. Is the new DFS what you are talking about? – Jackson Tale Apr 05 '13 at 16:10
0
  1. It is worthy to do that if you cannot find good codes in open source community. Do not reinvent wheels.

  2. There is another post has an extensive explanation on DFS algorithm by OCaml, Topological sort in OCaml What I suggest is to try write bfs, bfs_current and bfs_child into a single function.

Community
  • 1
  • 1
Indicator
  • 361
  • 2
  • 13
  • thanks for your advice, I am learning OCaml, so want to write as many thing as possible for myself. – Jackson Tale Apr 05 '13 at 14:15
  • I am not sure how to combine bfs, bfs_current and bfs_child into one function. And also are you sure that you are talking about `bfs`, not `dfs`? – Jackson Tale Apr 05 '13 at 14:21