If the list is long, you may want to use a hash table instead of a list for storing the visited cells and performing the lookups in near-constant time.
Let me expand and modify Pascal's code:
let rec is_cyclic list already_visited =
match list with
Nil -> false
| Cons(h, { contents = t }) ->
V.mem already_visited h ||
is_cyclic t (V.add already_visited h)
The V module comes from the following functor application:
module V = Visits.Make (struct type t = int end)
and Visits is defined as follows:
(* visits.ml *)
struct
module Make (X : sig type t end) :
sig
type elt
type t
val create : int -> t
val mem : t -> elt -> bool
val add : t -> elt -> unit
end with type elt = X.t =
struct
module H = Hashtbl.Make (
struct
type t = X.t
let equal = ( == )
let hash = Hashtbl.hash
end
)
type elt = X.t
type t = unit H.t
let create len = H.create len
let mem tbl x = H.mem tbl x
let add tbl x = H.add tbl x ()
end
end
The implementation above is perfectly safe and future-proof but is not polymorphic unlike the list-based solution.
It is possible to write a polymorphic version that uses the infamous Obj module, which should not be used without knowing a number of things that are not officially documented. The use of Obj in the code below makes assumptions on the implementation of the Hashtbl module, which are unlikely to break in the future but you are being warned.
That said, it is polymorphic and therefore easy to use:
(* visits.mli *)
type 'a t
val create : int -> 'a t
val mem : 'a t -> 'a -> bool
val add : 'a t -> 'a -> unit
(* visits.ml *)
module H = Hashtbl.Make (
struct
type t = Obj.t
(* Warning: using Obj is not pure OCaml. It makes assumptions
on the current implementation of Hashtbl,
which is unlikely to change in incompatible ways
anytime soon. *)
let equal = ( == )
let hash = Hashtbl.hash
end
)
type 'a t = unit H.t
let create len = H.create len
let mem : 'a t -> 'a -> bool = fun tbl x -> H.mem tbl (Obj.repr x)
let add : 'a t -> 'a -> unit = fun tbl x -> H.add tbl (Obj.repr x) ()