【问题标题】:checking whether mutable list has cycle in ocaml?检查可变列表是否在ocaml中有循环?
【发布时间】:2011-07-24 21:20:05
【问题描述】:

我正在尝试编写一个函数来测试 Ocaml 中的可变列表是否包含循环(即具有对自身的引用并不断重复。

我的列表定义为type '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 ;;

但这不太正确,我不确定如何从这里开始...感谢您的帮助!

【问题讨论】:

    标签: ocaml cyclic


    【解决方案1】:

    只要两个 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
          ...
    

    【讨论】:

    • 如果你还不熟悉 List.memq: 'a -> 'a list -> bool,一个很好的练习是使用==将它自己编写为递归函数。跨度>
    • 它看起来像一个 O(n^2) 函数。我更喜欢使用哈希表进行查找,或者使用 Victor 给出的函数。
    • @Laurent 实际上,我需要一个在我的项目中处理物理平等的哈希表。写完后,能不能把它发布到某个地方?
    • @Pascal:我说的是算法,在线性时间内已经有另外两个答案了。我的观点是维克多的解决方案似乎更好。但是,由于我们只需要对地址进行哈希处理,我想转换为整数将是一种解决方案(好吧,它是 Obj.magic,但它看起来很安全)。在某些情况下,迁移到 F# 可能是另一种解决方案...
    • @Laurent 实际上,我认为在 OCaml 运行时的支持下,我很有可能最终获得 == 哈希表。目前它在我的 TODO 列表(或 OCaml 实现者)上还不够高。
    【解决方案2】:

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

    让我扩展和修改 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) ()
    

    【讨论】:

    • 一般来说,人们对哈希函数的期望有两点:1) 与使用中的相等性兼容,而且 2) 能够很好地向不同的哈希发送不相等的值。 Hashtbl.hash, (==) 对在 2) 处不是很好——一般来说——因为结构上相同、物理上不同的值之间的冲突可能会大量发生。但它比一个不介意必须重新散列一些对象并将他的散列函数实现为fun x -> Hashtbl.hash ((Obj.magic x):int) 的同事要好(它没有做它试图做的事情,你明白为什么吗?)
    • 我不明白你的反对意见。在最坏的情况下(所有值在结构上都相等),它大致相当于使用 List.memq/List.assq,例如使用let rec x = 1 :: 1 :: 1 :: 1 :: 1 :: 1 :: 1 :: 1 :: x in x。也许没有任何节点标识符或标签的图结构将是最坏情况的现实示例。关键是我们不能使用指针值进行散列,因为它们被 GC 改变了。现在我不明白fun x -> Hashtbl.hash ((Obj.magic x):int)Hashtbl.hash 有何不同。你的意思是fun x -> (Obj.magic x : int) 很明显
    • 你的意思是fun x -> (Obj.magic x : int) 显然行不通吗?
    • "现在我不明白fun x -> Hashtbl.hash ((Obj.magic x):int)Hashtbl.hash 有什么不同" 这是同事犯的错误。他认为它会做一些不同的事情。他最终得到了Hashtbl.hash,除了碰撞问题之外还可以,但这不是他的本意。
    • 根据有见地的评论,我改变了我的功能。
    【解决方案3】:

    Pascal Cuoq's answer 是最好的一个,但为了避免轶事,您还可以通过保留两个游标并将其中一个游标以两倍于其他,一旦相等就停止:

    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  
    

    【讨论】:

      猜你喜欢
      • 2011-07-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-07-28
      • 2015-05-28
      • 1970-01-01
      相关资源
      最近更新 更多