Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable identification of cycle-creating edge during topographical sort (CCGraph) #287

Open
cemerick opened this issue Feb 5, 2020 · 6 comments

Comments

@cemerick
Copy link

cemerick commented Feb 5, 2020

I had need of a topographical sort that additionally reported which edge happened to offend in the case of a cyclic graph. To get there, I added this to my module:

module Topo (V : sig type t val pp : Format.t -> t -> unit end) = struct
  (* this code copy-pasted from CCGraph so that we can raise an exception on cycles
     that includes the offending `Back edge *)
  open CCGraph

  exception Cycle of V.t * V.t
  let _ =
    Printexc.register_printer
      (function
        | Cycle (src, dst) -> Some (Format.asprintf "Cycle (%a -> %a)" V.pp src V.pp dst)
        | _ -> None)

  let topo_sort_tag ~eq ?(rev=false) ~tags ~graph iter =
    (* use DFS *)
    let l =
      Traverse.Event.dfs_tag ~eq ~tags ~graph iter
      |> Iter.filter_map
        (function
          | `Exit v -> Some v
          | `Edge (src, _e, dst, `Back) -> raise (Cycle (src, dst))
          | `Enter _
          | `Edge _ -> None
        )
      |> Iter.fold (fun acc x -> x::acc) []
    in
    if rev then List.rev l else l
  
  let topo_sort ~eq ?rev ~tbl ~graph iter =
    let tags = {
      get_tag=tbl.mem;
      set_tag=(fun v -> tbl.add v ());
    } in
    topo_sort_tag ~eq ?rev ~tags ~graph iter
end

As you can see, the bodies of these two functions are effectively identical to that found in CCGraph.ml now, except that the exception raised on a cycle includes the `Back edge.

I'd be happy to produce a PR for this if you'd like, but:

  1. it seems like it would be a breaking change
  2. I can imagine wanting to include the label (_e) from the offending edge in the Cycle exception as well?
@c-cube
Copy link
Owner

c-cube commented Feb 8, 2020

Wouldn't you want the full cycle, in your use case? I think it could be done by maintaining a stack at enter/exit events, but it probably deserves to be in a different function (which also means it doesn't break compat).

@cemerick
Copy link
Author

cemerick commented Feb 8, 2020

I do, and you're right that maintaining a stack as one approach, but then you're talking about a completely different (and more expensive) topo sort routine.

I haven't gotten around to it quite yet, but I can get the full cycle otherwise (snip the offending edge reported by the exception and then depth-first-search back to the node on the other side), and I figured that would be preferable to maintaining a separate topo routine. 🤷‍♂️

@c-cube
Copy link
Owner

c-cube commented Feb 10, 2020

A common trick I do for retrocompat is make the actual implem the most general one, and reimplement the old function in terms of the new one (ie. with a with Cycle_full _ -> raise Cycle)

@c-cube
Copy link
Owner

c-cube commented Sep 7, 2022

is this still relevant @cemerick ?

@cemerick
Copy link
Author

cemerick commented Sep 7, 2022

A fair question; I'm not sure. I'll try to look again in this area early next week.

@cemerick
Copy link
Author

cemerick commented Dec 9, 2022

FWIW, this does still seem like a worthwhile addition, but is no longer relevant for me (I've moved all our graph stuffs to graphlib/ocamlgraph since opening this issue). Whether to leave it open or close (for now?) I leave up to you. :-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants