检查可变列表在ocaml中是否有循环?

检查可变列表在ocaml中是否有循环?,ocaml,cyclic,Ocaml,Cyclic,我试图编写一个函数来测试Ocaml中的可变列表是否包含一个循环(也就是说,有一个对自身的引用并不断重复) 我的列表定义为类型'a m_list=Nil | Cons of'a*('a m_list)ref) 到目前为止,我已经: let is_cyclic list = let rec check xs = match (!xs) with |Nil -> false |Cons(_,v) -> ((!v)==list)||check v in

我试图编写一个函数来测试Ocaml中的可变列表是否包含一个循环(也就是说,有一个对自身的引用并不断重复)

我的列表定义为
类型'a m_list=Nil | Cons of'a*('a m_list)ref)

到目前为止,我已经:

let is_cyclic list =
  let rec check xs = 
    match (!xs) with
     |Nil -> false
     |Cons(_,v) -> ((!v)==list)||check v in
  match list with
   |Nil -> false
   |Cons(_, x) -> check x ;;

但这并不完全正确,我不确定如何从这里开始……感谢您的帮助!

列表中有一个循环,只要有两个Cons单元格(在列表中的不同深度找到)相同。示例代码仅检查第一个Cons单元格是否再次出现在列表中。检查周期的一种方法是记住列表中访问过的所有Cons单元格,并将每个新单元格与以前的所有单元格进行比较

我不打算编写整个函数,但它可能是这样的:

let rec is_cyclic list already_visited =
  match list with
    Nil -> false
  | Cons(h, { contents = t }) -> 
    if List.memq list already_visited
    then (* list was traversed before *)
      ...
    else
      ...
是最好的一个,但为了避免轶事的模糊性,您也可以使用恒定内存执行此检查(但计算成本较高),方法是保留两个游标,其中一个游标的前进速度是另一个游标的两倍,并且在它们相等时立即停止:

let rec is_cyclic a b =    
  match a with 
    | Nil -> false 
    | Cons (_, { contents = a }) ->
      match b with 
        | Nil | Cons (_, { contents = Nil }) -> false
        | Cons (_, { contents = Cons (_, {contents = b}) } ->
          a == b || is_cyclic a b 

let is_cyclic l = is_cyclic l l  

如果列表很长,您可能希望使用哈希表而不是列表来存储访问的单元格并在几乎恒定的时间内执行查找

让我展开并修改Pascal的代码:

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)
V模块来自以下函子应用程序:

module V = Visits.Make (struct type t = int end)
访问的定义如下:

(* 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
上面的实现是完全安全且经得起未来考验的,但与基于列表的解决方案不同,它不是多态的

可以编写一个使用臭名昭著的Obj模块的多态版本,在不知道大量未正式记录的情况下,不应使用该模块。下面代码中使用Obj对Hashtbl模块的实现进行了假设,这在将来不太可能打破,但您会收到警告

也就是说,它是多态的,因此易于使用:

(* 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) ()

如果您还不熟悉List.memq:'a->'a List->bool,一个很好的练习是使用
=
将其作为递归函数编写。它看起来像一个O(n^2)函数。我更喜欢使用哈希表进行查找,或者使用Victor给出的函数。@Laurent事实上,我的项目中需要一个物理相等的哈希表。当你写完它后,你能把它发布到某个地方吗?@Pascal:我在说算法,线性时间内已经有两个其他答案了。我的观点是Victor的解决方案似乎更好。然而,由于我们只需要散列地址,我认为将其转换为整数将是一个解决方案(好的,这是Obj.magic,但看起来很安全)。在某些情况下,转移到F#可能是另一种解决方案…@Laurent事实上,我认为很有可能最终从OCaml运行时获得==哈希表的支持。目前,它在我的待办事项列表(或OCaml实现者)中还不够高。通常,人们期望从哈希函数中得到的两件事是1)要与使用中的相等兼容,还要2)做好向不同哈希发送非相等值的工作。Hashtbl.hash对(==)通常在2处不是很好,因为结构相同、物理上不同的值之间可能会发生大量冲突。但这比一位同事不介意对一些对象进行重新格式化并将其哈希函数实现为
fun x->Hashtbl.hash((Obj.magic x):int)
(这与它试图做的不一样,你知道为什么吗?)要好。我不理解你的反对意见。在最坏的情况下(所有值在结构上都相等),它大致相当于使用List.memq/List.assq,例如使用
让rec x=1::1::1::1::1::1::1::1::x In x
。没有任何节点标识符或标签的图结构可能是最坏情况下的一个现实例子。关键是我们不能使用指针值进行散列,因为它们是由GC变异的。现在我不明白
funx->Hashtbl.hash((Obj.magic x):int)
Hashtbl.hash
有什么不同。你的意思是
funx->(Obj.magic x:int)
哪一个显然是
funx->(Obj.magic x:int)
哪一个显然不起作用?”现在我不明白
funx->Hashtbl.hash((Obj.magic x:int)
Hashtbl.hash
“这是同事犯的错误。他认为它会做一些不同的事情。他最终得到了
Hashtbl.hash
,除了冲突问题之外,这还行,但这不是他想要的。在一次有见地的评论之后,我改变了我的函数。