A simple Priority Queue in OCaml

Sat, Dec 30, 2023 tags: [ Programming Ocaml ]

While working on some programming quiz in OCaml, I needed to use a priority queue (or heap) in order to solve some graph traversal problem. However, unfortunately, OCaml doesn’t provide any sort of priority queue or heap in its standard library. And after a brief search I haven’t found any that satisfied me in terms of their interface.

However, in I came across the idea to simply use a Set keyed by (int * 'a) where int is the priority and 'a the payload. That’s clever! The access complexity for the min (or max) element is now log N but that is still better than not having a proper heap. I also didn’t feel like writing my own heap based on an array or a simple tree structure; making use of the well-known Base.Set module seemed more attractive. I generally use Jane Street’s Base and Core all the time, as they are a bit more pleasant to use and more complete than the standard library.

It’s reasonably easy to implement such a queue for just one type, but coming up with a generic approach that works for more than one data type was a bit more difficult; it involved wrangling with first-class modules and comparators for a bit. Being a bit rusty with those, it took me the better part of a morning to arrive at a satisfactory solution. I’ve done some research afterwards, and the interface is actually fairly similar to the bheap package, which I could have used in the first place.

Also, the OCaml user manual (ironically, after searching and not finding a library implementation) uses a PrioQueue as example to demonstrate basic modules. A basic heap implementation is shown, demonstrating how easy it is implemented in elegant OCaml. However, it’s only implemented for int, whereas the implementation below works for any type with a Comparator.S implementation. (not that it would be very difficult to adapt the manual’s example to work with any type).

The approach I took is generic over any datatype that comes in a module providing compare and sexp_of_t and is slightly less elegant than Base.Set (with comparator witness etc.) but more easily understandable to the more fresh-faced OCaml programmers like me. It is essentially the structure used by the Stdlib container types, which at this point I had already forgotten and was thus forced to rediscover:

(* Simple approach: use a functor! Comparator.S is prevalent through existing data types. *)
module PrioQueue (Inner : Comparator.S) = struct
  (* This is the new element type, wrapping both a priority and the payload.
     Most importantly, it provides a compare function, and also `sexp_of_t` as required
     by Base.Set.
  *)
  module Elt = struct
    (* just a tuple is enough *)
    type t = int * Inner.t

    (* First compare priorities, then fall back to payload comparison. *)
    let compare (a, x) (b, y) =
      match Int.compare a b with 0 -> Inner.comparator.compare x y | x -> x

    let sexp_of_t (a, x) =
      Sexp.List [ Int.sexp_of_t a; Inner.comparator.sexp_of_t x ]
  end

  (* Comparator for Set: The Comparator complication wants to take the existing
     element module `Elt` and add a `comparator` definition based on its
     `compare` and `sexp_of_t` functions.
    *)
  module EltComp = struct
    include Comparator.Make (Elt)

    type t = Elt.t
  end

  (* Now we can write down both element type and comparator witness type. *)
  type t = Set.M(EltComp).t

  (* The priority queue (heap) functionality now basically comes for free. *)
  let empty : t = Set.empty (module EltComp)
  let is_empty = Set.is_empty
  let add q ~prio elt = Set.add q (prio, elt)
  let min_elt_exn q = snd (Set.min_elt_exn q)

  let remove_min_elt_exn q =
    let ((_, elt) as min) = Set.min_elt_exn q in
    (Set.remove q min, elt)

  let sexp_of_t q =
    let elts = Set.to_list q in
    Sexp.List (List.map elts ~f:Elt.sexp_of_t)

  (* add more of your liking ... *)
end

This can already be used with Base data types:

(* Declaration necessary for every data type to be contained. *)
module Int_pq = PrioQueue (Int)

let () =
  let q = Int_pq.empty in
  let q = Int_pq.add q ~prio:1 1 in
  let q = Int_pq.add q ~prio:2 2 in
  let q = Int_pq.add q ~prio:0 3 in
  assert (Int_pq.min_elt_exn q = 3)

However, we run into (slight) trouble when declaring our own module. We’d need to use the annoying indirection described in Real World OCaml where a module includes the inner module T for the entire functionality, and then uses Comparator.Make to provide the comparison functions.

This can be helped by a small wrapper functor:

module type Comparable = sig
  type t [@@deriving sexp, compare]
end

module PrimPrioQueue (Inner : Comparable) = struct
  module CompPos = struct
    include Inner
    include Comparable.Make (Inner)
  end

  include PrioQueue (CompPos)
end

A “primitive” priority queue PrimPrioQueue can now be created from any module containing as little as compare and sexp_of_t, where PrimPrioQueue is now responsible for building the comparator into the module. The inner type is still transparent.

Using this approach worked with no more nasty surprises, probably a bit slower than a true heap, but robust enough to not have to think about it anymore after the initial setup.