package splay_tree

  1. Overview
  2. Docs

Source file splay_tree0_intf.ml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
open Core

(** Splay trees are binary search trees that move recently accessed nodes closer to the
    root for easier access.  They have amortized O(log n)-time access for a large enough
    sequence of primitive operations.

    As a heuristic, a splay tree may outperform other trees such as red-black trees when
    recently accessed items are more likely to be accessed in the near future.

    The amortized complexity analysis only applies if [t] is used as a linear type, i.e.
    each [t] returned by access operations is used for the next operation, instead of
    being discarded (similar to Fqueue). If, instead, it is used as a persistent data
    structure, most primitive operations have O(n) complexity.
*)

module type Key = sig
  type t [@@deriving sexp, compare]
end

module type Data = sig
  type t [@@deriving sexp]
end

module type Reduction_operation = sig
  type key
  type data
  type accum

  val identity : accum
  val singleton : key:key -> data:data -> accum

  (** [combine] is required to be associative and have [identity] as its identity.
      In other words, they must form a monoid. *)
  val combine : accum -> accum -> accum
end

type ('k, 'd, 'a) reduction_operation =
  (module Reduction_operation with type key = 'k and type data = 'd and type accum = 'a)

module type S = sig
  type t [@@deriving sexp]
  type key [@@deriving sexp]
  type data [@@deriving sexp]
  type accum

  val empty : t
  val of_alist : (key * data) list -> t Or_error.t
  val of_alist_exn : (key * data) list -> t
  val to_alist : t -> (key * data) list
  val is_empty : t -> bool
  val length : t -> int
  val accum : t -> accum
  val keys : t -> key list
  val data : t -> data list
  val mem : t -> key -> bool
  val find : t -> key -> data option
  val set : t -> key:key -> data:data -> t
  val remove : t -> key -> t
  val remove_min : t -> (key * data * t) option
  val remove_max : t -> (key * data * t) option
  val remove_after : t -> key -> (key * data * t) option
  val remove_before : t -> key -> (key * data * t) option
  val map : t -> f:(data -> data) -> t

  val map_range
    :  t
    -> min_key:key
    -> max_key:key
    -> f:((key * data) list -> (key * data) list)
    -> t

  val nth : t -> int -> (key * data) option

  (** [rank t key] is the number of nodes before where [key] would be inserted. In other
      words, the length of the left subtree after a [split t key]. *)
  val rank : t -> key -> int

  (** [search] implements bisection search over [t] based on the [accum] values
      of its prefixes.

      Let's consider a [t] consisting of four elements [a; b; c; d] (see diagram below)
      (we'll refer to all of key, data, and accum of [a] as just [a]; we'll also assume
      [R.combine = (+)]).

      Then there are five possible positions to evaluate [f] at (numbered 0..4 below):

      +
      | position:       0     1     2     3     4
      |                 -------------------------
      | element:        |  a  |  b  |  c  |  d  |
      |                 -------------------------
      | left:           0     a    a+b  a+b+c a+b+c+d
      | right:      a+b+c+d b+c+d  c+d    d     0
      | f ~left ~right: R     R     R     L     L
      +

      The function [f ~left ~right] will be called for a particular position and it takes
      the sum of the elements to the left and to the right of this position.
      This means [left + right] will be equal to [accum t].

      The return value of [f] must indicate the direction where the desired element is.
      In the diagram above [f ~left:(a+b) ~right:(c+d)] returns [`Right],
      whereas [f ~left:(a+b+c) ~right:d] returns [`Left], which makes [c] the desired
      element.

      Therefore, [search] will return [c]. If [f] returns the same value at all positions,
      [search] returns [None].

      For it to make sense, [f] must be monotonic: calling [f] on every possible position
      from left to right should produce a prefix of [`Right] values followed by a
      suffix of [`Left] values.

      Example:

      If the values are positive integers and reduction operation is sum, then you can
      find the last node where the prefix sum before it is at most [x] with the
      following [f]:
      {[
        let f ~left ~right:_ = if x < left then `Left else `Right
      ]}
  *)
  val search
    :  t
    -> f:(left:accum -> right:accum -> [ `Right | `Left ])
    -> (key * data) option

  module Partition : sig
    type nonrec t =
      { (* [lt] = values less than the [min_key] of the partition *)
        lt : t
      ; (* [mid] = values between [min_key] and [max_key] *)
        mid : t
      ; (* [gt] = values greater than the [max_key] of the partition *)
        gt : t
      }
  end

  val partition : ?min_key:key -> ?max_key:key -> t -> Partition.t

  (** [subrange t ?min_key ?max_key] is equivalent to the [mid] in [partition] *)
  val subrange : ?min_key:key -> ?max_key:key -> t -> t

  val merge
    :  t
    -> t
    -> f:
         (key:key
          -> [ `Left of data | `Right of data | `Both of data * data ]
          -> data option)
    -> t

  val split : t -> key -> t * data option * t

  (** [join t1 t2] directly concatenates the sequences described by [t1] and [t2]. This
      should be used to rejoin trees obtained by a split operation, though it can be used
      for other things.

      This operation can fail if the concatenation of the two sequences is invalid; i.e.
      the keys are not in order. This happens when the maximum key of [t1] is at or above
      the minimum key of [t2].

      Currently the cost of [join] is not fully amortized, so it should be considered
      worst-case linear time.
  *)
  val join : t -> t -> t Or_error.t

  val join_exn : t -> t -> t
end

module type Splay_tree = sig
  module type Key = Key
  module type Data = Data
  module type Reduction_operation = Reduction_operation

  type nonrec ('k, 'd, 'a) reduction_operation = ('k, 'd, 'a) reduction_operation

  module type S = S

  module Make_with_reduction
    (Key : Key)
    (Data : Data)
    (R : Reduction_operation with type key = Key.t and type data = Data.t) :
    S with type key = Key.t and type data = Data.t and type accum = R.accum

  module Make_without_reduction (Key : Key) (Data : Data) :
    S with type key = Key.t and type data = Data.t and type accum = unit

  module Reduction_operations : sig
    val reduce2
      :  ('k, 'd, 'a) reduction_operation
      -> ('k, 'd, 'b) reduction_operation
      -> ('k, 'd, 'a * 'b) reduction_operation
  end
end