1

我正在阅读实用逻辑和自动推理手册。它有一些代码来定义文件中的有限部分函数 lib.ml。我无法理解部分函数中重新定义和组合的代码的含义。子功能的目的是什么newbranch?代码如下:

type ('a,'b)func =
    Empty
  | Leaf of int * ('a*'b)list
  | Branch of int * int * ('a,'b)func * ('a,'b)func;;

(* ------------------------------------------------------------------------- *)
(* Redefinition and combination.                                             *)
(* ------------------------------------------------------------------------- *)

let (|->),combine =
  let newbranch p1 t1 p2 t2 =
    let zp = p1 lxor p2 in
    let b = zp land (-zp) in
    let p = p1 land (b - 1) in
    if p1 land b = 0 then Branch(p,b,t1,t2)
    else Branch(p,b,t2,t1) in
  let rec define_list (x,y as xy) l =
    match l with
      (a,b as ab)::t ->
      let c = Pervasives.compare x a in
      if c = 0 then xy::t
      else if c < 0 then xy::l
      else ab::(define_list xy t)
    | [] -> [xy]
  and combine_list op z l1 l2 =
    match (l1,l2) with
      [],_ -> l2
    | _,[] -> l1
    | ((x1,y1 as xy1)::t1,(x2,y2 as xy2)::t2) ->
      let c = Pervasives.compare x1 x2 in
      if c < 0 then xy1::(combine_list op z t1 l2)
      else if c > 0 then xy2::(combine_list op z l1 t2) else
        let y = op y1 y2 and l = combine_list op z t1 t2 in
        if z(y) then l else (x1,y)::l in
  let (|->) x y =
    let k = Hashtbl.hash x in
    let rec upd t =
      match t with
        Empty -> Leaf (k,[x,y])
      | Leaf(h,l) ->
        if h = k then Leaf(h,define_list (x,y) l)
        else newbranch h t k (Leaf(k,[x,y]))
      | Branch(p,b,l,r) ->
        if k land (b - 1) <> p then newbranch p t k (Leaf(k,[x,y]))
        else if k land b = 0 then Branch(p,b,upd l,r)
        else Branch(p,b,l,upd r) in
    upd in
  let rec combine op z t1 t2 =
    match (t1,t2) with
      Empty,_ -> t2
    | _,Empty -> t1
    | Leaf(h1,l1),Leaf(h2,l2) ->
      if h1 = h2 then
        let l = combine_list op z l1 l2 in
        if l = [] then Empty else Leaf(h1,l)
      else newbranch h1 t1 h2 t2
    | (Leaf(k,lis) as lf),(Branch(p,b,l,r) as br) ->
      if k land (b - 1) = p then
        if k land b = 0 then
          (match combine op z lf l with
             Empty -> r | l' -> Branch(p,b,l',r))
        else
          (match combine op z lf r with
             Empty -> l | r' -> Branch(p,b,l,r'))
      else
        newbranch k lf p br
    | (Branch(p,b,l,r) as br),(Leaf(k,lis) as lf) ->
      if k land (b - 1) = p then
        if k land b = 0 then
          (match combine op z l lf with
             Empty -> r | l' -> Branch(p,b,l',r))
        else
          (match combine op z r lf with
             Empty -> l | r' -> Branch(p,b,l,r'))
      else
        newbranch p br k lf
    | Branch(p1,b1,l1,r1),Branch(p2,b2,l2,r2) ->
      if b1 < b2 then
        if p2 land (b1 - 1) <> p1 then newbranch p1 t1 p2 t2
        else if p2 land b1 = 0 then
          (match combine op z l1 t2 with
             Empty -> r1 | l -> Branch(p1,b1,l,r1))
        else
          (match combine op z r1 t2 with
             Empty -> l1 | r -> Branch(p1,b1,l1,r))
      else if b2 < b1 then
        if p1 land (b2 - 1) <> p2 then newbranch p1 t1 p2 t2
        else if p1 land b2 = 0 then
          (match combine op z t1 l2 with
             Empty -> r2 | l -> Branch(p2,b2,l,r2))
        else
          (match combine op z t1 r2 with
             Empty -> l2 | r -> Branch(p2,b2,l2,r))
      else if p1 = p2 then
        (match (combine op z l1 l2,combine op z r1 r2) with
           (Empty,r) -> r | (l,Empty) -> l | (l,r) -> Branch(p1,b1,l,r))
      else
        newbranch p1 t1 p2 t2 in
  (|->),combine;;

你能为我解释一下这段代码的含义吗?

4

1 回答 1

6

这些树被称为 Patricia 树,它们是Radix 树的一种形式。John Harrison 将它们用作有限部分函数,​​即不可变哈希树,它们是部分函数的唯一表示。

要了解功能,您必须了解树。

树的解释

| Leaf of int * ('a*'b)list

叶子中的 int 参数是键的哈希值。哈希值之后的列表是键值对的排序关联列表,其中所有键都具有相同的哈希值。这些哈希值的小端前缀将存储在分支中。

如果k = 00111001在二进制大端表示法中,则1001是 的大端后缀或小端前缀k。由于所有整数都有固定的位数,因此前缀用 zeor 扩展。前缀变成00001001了程序中的东西。

| Branch of int * int * ('a,'b)func * ('a,'b)func

您应该将这些整数视为 little-endian 位数组,而不是整数。第一个 int 是两个子树的公共 little-endian 前缀。第二个 int 标记不同的第一位的位置。这两个整数用作位掩码。在Branch (p, b, t1, t2)树中有一个前缀,该前缀在 position 处以at1扩展。这棵树也有一个扩展但在位置的前缀。p0bt2p1b

以下是一些理解代码的规则:

  • 每当你看到一个t,t1或者t2它是一棵帕特里夏树。(类型('a, 'b) func
  • 变量p或表示哈希值p1p2二进制小端前缀(= 大端后缀)。(类型int
  • 变量bb1b2标记不同的第一位。(类型int)只有一点,如果我没记错的话1,其他的是。0
  • 变量c是比较的结果,即只有=0>0重要<0,而不是实际值。(类型int
  • 该变量k是键的哈希值。(类型int

功能说明newbranch

let newbranch p1 t1 p2 t2 =

我们想将两棵树t1和分别t2与前缀p1和结合起来p2。假设是这两个前缀不同。

let zp = p1 lxor p2 in
let b = zp land (-zp) in

获得第一位是有点摆弄p1p2实际上是不同的。它使用整数的算术负数是使用二进制补码和增量实现的事实。在大端二进制表示法中: Ifp1 = 00111001p2 = 00011001then b = 00100000

let p = p1 land (b - 1) in

现在b它只包含一个位,b - 1直到那个位位置,例如,如果b = 00100000是二进制,那么b-1 = 00011111。所以可以用作位掩码来获取和输出b-1的常见小端前缀(=大端后缀)。请注意,您也可以在此处使用,因为它是通用前缀。p1p2p1p2

现在我们已经计算了公共前缀和位位置,计算新的唯一需要的Branch是两个子树的顺序:

if p1 land b = 0 then Branch(p,b,t1,t2)
else Branch(p,b,t2,t1)

如果在位p1有 a ,那么它首先出现。0b

记住这个解释,就很容易理解其余的代码。测试用例和打印一些变量值会对你有很大帮助。

该功能combine涉及更多,因为注意不要让任何分支空着!树应该是有限偏函数的唯一表示。当且仅当并且表示相同的函数t1 = t2时,帕特里夏树上的表达式才会返回。这解释了像.truet1t2match .. with Empty -> ..

功能说明define_list

该函数define_list将一个键值对插入到没有重复键(x, y)的排序列表中。l如果键x已经在列表中,则替换它的值。当然l没有修改,但返回一个新的修改列表。毕竟这是函数式编程。该列表l根据键排序,Pervasives.compare用于比较键。

match l with
  (a,b as ab)::t ->
    let c = Pervasives.compare x a in
    if c = 0 then xy::t

如果(a, b)land 的头x = a(与c = 0),则(x, y)替换(a, b). (因为ax在这种情况下是相等的,您可以使用(a, y)来替换(a, b),但是您将构建一个新的对。xy出于性能原因,明确使用该名称来避免构建新的对。)

  else if c < 0 then xy::l
  else ab::(define_list xy t)

否则,根据 是小于ie还是大于 ie ,在(x, y)之前或之后插入该对。(a, b)xac < 0ac > 0

John Harrison 可以使用表达式x = a而不是c = 0 andx < a来代替,c < 0但是他会使用两个隐式调用的比较Pervasives.compare而不是一个。该函数Pervasives.compare可能很慢,因为它递归地下降到任意值。该表达式c > 0只是整数的比较,所以它很快。

该函数define_list仅在哈希冲突期间使用。

功能说明|->

该函数的|->作用几乎与define_list树而不是列表相同。您可以调用它define_treedefine_finite_partial_function. 我猜如果 OCaml 有 Unicode 标识符,John Harrison 会使用 maplet 箭头:↦(LaTeX 中的 \mapsto)。该表达式(x |-> y) t构建了一个有限的偏函数,t除了 keyx现在映射到 value之外y

let (|->) x y =
  let k = Hashtbl.hash x in
  let rec upd t =

定义下降到树中,但不需要一遍又一遍地重新计算哈希值kx首先计算它,然后递归发生在upd委托其大部分工作的本地函数内。

模式匹配的前两种情况几乎是不言自明的:

match t with
    Empty -> Leaf (k,[x,y])
  | Leaf(h,l) ->
    if h = k then Leaf(h,define_list (x,y) l)
    else newbranch h t k (Leaf(k,[x,y]))
  • 如果没有内容,只需创建一个叶子。请记住,[x,y]这与[(x, y)]. 省略括号只是不好的风格。(包含两个元素的列表将是[x; y]。)
  • 如果存在具有相同哈希值的叶子(哈希冲突),则键值对(x, y)进入该叶子的列表。这是 的工作define_list
  • 如果哈希值不匹配,则找到哈希值的公共前缀并创建一个新分支。这是 的工作newbranch。([x,y][(x, y)]

对于最后一种情况,您必须记住如何阅读位摆弄:

| Branch(p,b,l,r) ->
    if k land (b - 1) <> p then newbranch p t k (Leaf(k,[x,y]))
    else if k land b = 0 then Branch(p,b,upd l,r)
    else Branch(p,b,l,upd r)
  • 如果k与前缀不匹配p,则查找新的公共前缀并创建新分支。([x,y][(x, y)]
  • 如果k匹配前缀p并且下一位是0,则下降到左子树。
  • 如果k匹配前缀p并且下一位是1,则下降到右子树。
于 2015-08-21T19:49:43.883 回答