Ocaml中可变变量的哈希表

men*_*agi 6 ocaml hashtable mutable

我需要在Ocaml中使用可变变量的哈希表,但它没有用.

let link = Hashtbl.create 3;;
let a = ref [1;2];;
let b = ref [3;4];;
Hashtbl.add link a b;;

# Hashtbl.mem link a;;
- : bool = true

# a := 5::!a;;
- : unit = ()

# Hashtbl.mem link a;;
- : bool = false
Run Code Online (Sandbox Code Playgroud)

有没有办法让它有效?

Jef*_*eld 8

您可以使用functorial接口,它允许您提供自己的哈希和相等函数.然后编写仅基于值的非可变部分的函数.在这个例子中,没有非可变部分.因此,您不希望在表格中找到什么.但是在一个更现实的例子中(根据我的经验),你可以使用不可变的部分.

如果没有任何不可变部分,您可以专门添加它们以用于散列.例如,您可以为每个值添加任意唯一的整数.

也可以基于物理相等(==)进行散列,它具有合理的引用定义(以及其他可变值).但是,你必须要小心,因为物理平等有点棘手.例如,您不能使用值的物理地址作为您的哈希键 - 由于垃圾回收,地址可以随时更改.


Gil*_*il' 5

可能碰巧具有相同内容的可变变量仍然可以被区分,因为它们存储在存储器中的不同位置.它们可以与物理相等运算符(==)进行比较.但是,OCaml没有提供比相等更好的东西,它不提供非平凡的散列函数或引用的顺序,因此您可以构建以存储引用的唯一数据结构是某种形式的关联列表,使用$\Theta( n)大多数用途的$访问时间.

(如果玩脏话,你实际上可以获得底层指针.但指针可以在你的脚下移动.尽管如此,有一种方法可以使用它,但如果你需要问,你不应该使用它.而你无论如何,对此并不是绝望.)

如果两个不同的引用具有不同的内容,则很容易比较引用.所以就这样吧!为引用添加唯一标识符.保留一个全局计数器,每次创建引用时将其递增1,并将计数器值与数据一起存储.现在您的引用可以通过其计数器值进行索引.

let counter = ref 0
let new_var x = incr counter; ref (!counter, x)
let var_value v = snd !v
let update_var v x = v := (fst !v, x)
let hash_var v = Hashtbl.hash (fst !v)
Run Code Online (Sandbox Code Playgroud)

为了更好地确保类型安全性和提高效率,请定义包含计数器值和项目的数据结构.

let counter = ref 0
type counter = int
type 'a variable = {
    key : counter;
    mutable data : 'a;
}
let new_var x = incr counter; {key = !counter; data = x}
let hash_var v = Hashtbl.hash v.key
Run Code Online (Sandbox Code Playgroud)

您可以将上面的代码放在模块中,并使counter类型为abstract.此外,您可以使用Hashtblfunctorial接口定义哈希表模块.这是使用更清晰,更模块化的结构在其上定义变量和哈希表结构的另一种方法.

module Counter = (struct
  type t = int
  let counter = ref 0
  let next () = incr counter; !counter
  let value c = c
end : sig
  type t
  val next : unit -> t
  val value : t -> int
end)
module Variable = struct
  type 'a variable = {
      mutable data : 'a;
      key : Counter.t;
  }
  let make x = {key = Counter.next(); data = x}
  let update v x = v.data <- x
  let get v = v.data
  let equal v1 v2 = v1 == v2
  let hash v = Counter.value v.key
  let compare v1 v2 = Counter.value v2.key - Counter.value v1.key
end
module Make = functor(A : sig type t end) -> struct
  module M = struct
    type t = A.t Variable.variable
    include Variable
  end
  module Hashtbl = Hashtbl.Make(M)
  module Set = Set.Make(M)
  module Map = Map.Make(M)
end
Run Code Online (Sandbox Code Playgroud)

我们所需要的中间模块Variable,因为类型variable是参数和标准库数据结构函子(Hashtbl.Make,Set.Make,Map.Make)仅用于类型构造不带参数的定义.这是一个接口,它公开了多态接口(带有相关的函数,但没有数据结构)和一个functor来构建任意数量的单态实例,带有相关的哈希表(和set和map)类型.

module Variable : sig
  type 'a variable
  val make : 'a -> 'a variable
  val update : 'a variable -> 'a -> unit
  val get : 'a variable -> 'a
  val equal : 'a -> 'a -> bool
  val hash : 'a variable -> int
  val compare : 'a variable -> 'b variable -> int
end
module Make : functor(A : sig type t end) -> sig
  module M : sig
    type t = A.t variable.variable
    val make : A.t -> t
    val update : t -> A.t -> unit
    val get : t -> A.t
    val equal : t -> t -> bool
    val hash : t -> int
    val compare : t -> t -> int
  end
  module Hashtbl : Hashtbl.S with type key = M.t
  module Set : Set.S with type key = M.t
  module Map : Map.S with type key = M.t
end
Run Code Online (Sandbox Code Playgroud)

请注意,如果您希望程序在运行期间生成超过2 ^ 30个变量,int则不会删除它.您需要两个int值来生成2 ^ 60位值,或者Int64.t.

请注意,如果您的程序是多线程的,则需要在计数器周围锁定,以使增量和查找原子.