mirror of
https://github.com/c-cube/ocaml-containers.git
synced 2025-12-06 03:05:28 -05:00
functional SplayTree as Maps
This commit is contained in:
parent
d0bb88e365
commit
6c1f7fb950
4 changed files with 309 additions and 0 deletions
198
splayMap.ml
Normal file
198
splayMap.ml
Normal file
|
|
@ -0,0 +1,198 @@
|
||||||
|
(*
|
||||||
|
Copyright (c) 2013, Simon Cruanes
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer. Redistributions in binary
|
||||||
|
form must reproduce the above copyright notice, this list of conditions and the
|
||||||
|
following disclaimer in the documentation and/or other materials provided with
|
||||||
|
the distribution.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*)
|
||||||
|
|
||||||
|
(** {1 Functional Maps} *)
|
||||||
|
|
||||||
|
(* We use splay trees, following
|
||||||
|
http://www.cs.cornell.edu/Courses/cs3110/2009fa/recitations/rec-splay.html
|
||||||
|
*)
|
||||||
|
|
||||||
|
|
||||||
|
type ('a, 'b) t = {
|
||||||
|
cmp : 'a -> 'a -> int;
|
||||||
|
mutable tree : ('a, 'b) tree; (* for lookups *)
|
||||||
|
} (** Tree with keys of type 'a, and values of type 'b *)
|
||||||
|
and ('a, 'b) tree =
|
||||||
|
| Empty
|
||||||
|
| Node of ('a * 'b * ('a, 'b) tree * ('a, 'b) tree)
|
||||||
|
|
||||||
|
let empty_with ~cmp =
|
||||||
|
{ cmp;
|
||||||
|
tree = Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
let empty () =
|
||||||
|
{ cmp = Pervasives.compare;
|
||||||
|
tree = Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
let is_empty t =
|
||||||
|
match t.tree with
|
||||||
|
| Empty -> true
|
||||||
|
| Node _ -> false
|
||||||
|
|
||||||
|
(** Pivot the tree so that the node that has key [key], or close to [key], is
|
||||||
|
the root node. *)
|
||||||
|
let rec splay ~cmp (k, v, l, r) key =
|
||||||
|
let c = cmp key k in
|
||||||
|
if c = 0
|
||||||
|
then (k, v, l, r) (* found *)
|
||||||
|
else if c < 0
|
||||||
|
then match l with
|
||||||
|
| Empty -> (k, v, l, r) (* not found *)
|
||||||
|
| Node (lk, lv, ll, lr) ->
|
||||||
|
let lc = cmp key lk in
|
||||||
|
if lc = 0
|
||||||
|
then (lk, lv, ll, Node (k, v, lr, r)) (* zig *)
|
||||||
|
else if lc < 0
|
||||||
|
then match ll with
|
||||||
|
| Empty -> (lk, lv, Empty, Node (k, v, lr, r)) (* not found *)
|
||||||
|
| Node n -> (* zig zig *)
|
||||||
|
let (llk, llv, lll, llr) = splay ~cmp n key in
|
||||||
|
(llk, llv, lll, Node (lk, lv, llr, Node (k, v, lr, r)))
|
||||||
|
else
|
||||||
|
match lr with
|
||||||
|
| Empty -> (lk, lv, ll, Node (k, v, Empty, r))
|
||||||
|
| Node n -> (* zig zag *)
|
||||||
|
let (lrk, lrv, lrl, lrr) = splay ~cmp n key in
|
||||||
|
(lrk, lrv, Node (lk, lv, ll, lrl), Node (k, v, lrr, r))
|
||||||
|
else match r with
|
||||||
|
| Empty -> (k, v, l, r) (* not found *)
|
||||||
|
| Node (rk, rv, rl, rr) ->
|
||||||
|
let rc = cmp key rk in
|
||||||
|
if rc = 0
|
||||||
|
then (rk, rv, Node (k, v, l, rl), rr) (* zag *)
|
||||||
|
else if rc > 0
|
||||||
|
then match rr with
|
||||||
|
| Empty -> (rk, rv, Node (k, v, l, rl), Empty) (* not found *)
|
||||||
|
| Node n -> (* zag zag *)
|
||||||
|
let (rrk, rrv, rrl, rrr) = splay ~cmp n key in
|
||||||
|
(rrk, rrv, Node (rk, rv, Node (k, v, l, rl), rrl), rrr)
|
||||||
|
else match rl with
|
||||||
|
| Empty -> (rk, rv, Node (k, v, l, Empty), rr) (* zag zig *)
|
||||||
|
| Node n -> (* zag zig *)
|
||||||
|
let (rlk, rlv, rll, rlr) = splay ~cmp n key in
|
||||||
|
(rlk, rlv, Node (k, v, l, rll), Node (rk, rv, rlr, rr))
|
||||||
|
|
||||||
|
let find t key =
|
||||||
|
match t.tree with
|
||||||
|
| Empty -> raise Not_found
|
||||||
|
| Node (k, v, l, r) ->
|
||||||
|
let (k, v, l, r) = splay ~cmp:t.cmp (k, v, l, r) key in
|
||||||
|
t.tree <- Node (k, v, l, r); (* save balanced tree *)
|
||||||
|
if t.cmp key k = 0
|
||||||
|
then v
|
||||||
|
else raise Not_found
|
||||||
|
|
||||||
|
let mem t key =
|
||||||
|
match t.tree with
|
||||||
|
| Empty -> false
|
||||||
|
| Node (k, v, l, r) ->
|
||||||
|
let (k, v, l, r) = splay ~cmp:t.cmp (k, v, l, r) key in
|
||||||
|
t.tree <- Node (k, v, l, r); (* save balanced tree *)
|
||||||
|
if t.cmp key k = 0
|
||||||
|
then true
|
||||||
|
else false
|
||||||
|
|
||||||
|
(** Recursive insertion of key->value in the tree *)
|
||||||
|
let rec insert ~cmp tree key value =
|
||||||
|
match tree with
|
||||||
|
| Empty -> Node (key, value, Empty, Empty)
|
||||||
|
| Node (k, v, l, r) ->
|
||||||
|
let c = cmp key k in
|
||||||
|
if c = 0
|
||||||
|
then Node (key, value, l, r) (* replace *)
|
||||||
|
else if c < 0
|
||||||
|
then Node (k, v, insert ~cmp l key value, r)
|
||||||
|
else Node (k, v, l, insert ~cmp r key value)
|
||||||
|
|
||||||
|
let add t key value =
|
||||||
|
let tree =
|
||||||
|
match t.tree with
|
||||||
|
| Empty -> Node (key, value, Empty, Empty)
|
||||||
|
| Node (k, v, l, r) ->
|
||||||
|
let (k, v, l, r) = splay ~cmp:t.cmp (k, v, l, r) key in
|
||||||
|
let tree = Node (k, v, l, r) in
|
||||||
|
t.tree <- tree; (* save balanced tree *)
|
||||||
|
(* insertion in this tree *)
|
||||||
|
insert ~cmp:t.cmp tree key value
|
||||||
|
in
|
||||||
|
{ t with tree; }
|
||||||
|
|
||||||
|
let singleton ~cmp key value =
|
||||||
|
add (empty_with ~cmp) key value
|
||||||
|
|
||||||
|
(** Merge of trees, where a < b *)
|
||||||
|
let rec left_merge a b =
|
||||||
|
match a, b with
|
||||||
|
| Empty, Empty -> Empty
|
||||||
|
| Node (k, v, l, r), b -> Node (k, v, l, left_merge r b)
|
||||||
|
| Empty, b -> b
|
||||||
|
|
||||||
|
let remove t key =
|
||||||
|
match t.tree with
|
||||||
|
| Empty -> t
|
||||||
|
| Node (k, v, l, r) ->
|
||||||
|
let (k, v, l, r) = splay ~cmp:t.cmp (k, v, l, r) key in
|
||||||
|
t.tree <- Node (k, v, l, r);
|
||||||
|
if t.cmp key k = 0
|
||||||
|
then (* remove the node, by merging the subnodes *)
|
||||||
|
let tree = left_merge l r in
|
||||||
|
{ t with tree; }
|
||||||
|
else (* not present, same tree *)
|
||||||
|
t
|
||||||
|
|
||||||
|
let iter t f =
|
||||||
|
let rec iter t = match t with
|
||||||
|
| Empty -> ()
|
||||||
|
| Node (k, v, l, r) ->
|
||||||
|
f k v;
|
||||||
|
iter l;
|
||||||
|
iter r
|
||||||
|
in iter t.tree
|
||||||
|
|
||||||
|
let fold t acc f =
|
||||||
|
let rec fold acc t = match t with
|
||||||
|
| Empty -> acc
|
||||||
|
| Node (k, v, l, r) ->
|
||||||
|
let acc = f acc k v in
|
||||||
|
let acc = fold acc l in
|
||||||
|
fold acc r
|
||||||
|
in
|
||||||
|
fold acc t.tree
|
||||||
|
|
||||||
|
let size t = fold t 0 (fun acc _ _ -> acc+1)
|
||||||
|
|
||||||
|
let choose t =
|
||||||
|
match t.tree with
|
||||||
|
| Empty -> raise Not_found
|
||||||
|
| Node (k, v, _, _) -> k, v
|
||||||
|
|
||||||
|
let to_seq t =
|
||||||
|
Sequence.from_iter
|
||||||
|
(fun kont -> iter t (fun k v -> kont (k, v)))
|
||||||
|
|
||||||
|
let of_seq t seq =
|
||||||
|
Sequence.fold (fun t (k, v) -> add t k v) t seq
|
||||||
69
splayMap.mli
Normal file
69
splayMap.mli
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
(*
|
||||||
|
Copyright (c) 2013, Simon Cruanes
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer. Redistributions in binary
|
||||||
|
form must reproduce the above copyright notice, this list of conditions and the
|
||||||
|
following disclaimer in the documentation and/or other materials provided with
|
||||||
|
the distribution.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*)
|
||||||
|
|
||||||
|
(** {1 Functional Maps} *)
|
||||||
|
|
||||||
|
type ('a, 'b) t
|
||||||
|
(** Tree with keys of type 'a, and values of type 'b *)
|
||||||
|
|
||||||
|
val empty_with : cmp:('a -> 'a -> int) -> ('a, 'b) t
|
||||||
|
(** Empty tree *)
|
||||||
|
|
||||||
|
val empty : unit -> ('a, 'b) t
|
||||||
|
(** Empty tree using Pervasives.compare *)
|
||||||
|
|
||||||
|
val is_empty : (_, _) t -> bool
|
||||||
|
(** Is the tree empty? *)
|
||||||
|
|
||||||
|
val find : ('a, 'b) t -> 'a -> 'b
|
||||||
|
(** Find the element for this key, or raises Not_found *)
|
||||||
|
|
||||||
|
val mem : ('a, _) t -> 'a -> bool
|
||||||
|
(** Is the key member of the tree? *)
|
||||||
|
|
||||||
|
val add : ('a, 'b) t -> 'a -> 'b -> ('a, 'b) t
|
||||||
|
(** Add the binding to the tree *)
|
||||||
|
|
||||||
|
val singleton : cmp:('a -> 'a -> int) -> 'a -> 'b -> ('a, 'b) t
|
||||||
|
(** Singleton map *)
|
||||||
|
|
||||||
|
val remove : ('a, 'b) t -> 'a -> ('a, 'b) t
|
||||||
|
(** Remove the binding for this key *)
|
||||||
|
|
||||||
|
val iter : ('a, 'b) t -> ('a -> 'b -> unit) -> unit
|
||||||
|
(** Iterate on bindings *)
|
||||||
|
|
||||||
|
val fold : ('a, 'b) t -> 'c -> ('c -> 'a -> 'b -> 'c) -> 'c
|
||||||
|
(** Fold on bindings *)
|
||||||
|
|
||||||
|
val size : (_, _) t -> int
|
||||||
|
(** Number of bindings (linear) *)
|
||||||
|
|
||||||
|
val choose : ('a, 'b) t -> ('a * 'b)
|
||||||
|
(** Some binding, or raises Not_found *)
|
||||||
|
|
||||||
|
val to_seq : ('a, 'b) t -> ('a * 'b) Sequence.t
|
||||||
|
|
||||||
|
val of_seq : ('a, 'b) t -> ('a * 'b) Sequence.t -> ('a, 'b) t
|
||||||
|
|
@ -6,6 +6,7 @@ let suite =
|
||||||
"all_tests" >:::
|
"all_tests" >:::
|
||||||
[ Test_pHashtbl.suite;
|
[ Test_pHashtbl.suite;
|
||||||
Test_PersistentHashtbl.suite;
|
Test_PersistentHashtbl.suite;
|
||||||
|
Test_splayMap.suite;
|
||||||
Test_leftistheap.suite;
|
Test_leftistheap.suite;
|
||||||
Test_cc.suite;
|
Test_cc.suite;
|
||||||
Test_puf.suite;
|
Test_puf.suite;
|
||||||
|
|
|
||||||
41
tests/test_splayMap.ml
Normal file
41
tests/test_splayMap.ml
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
|
||||||
|
open OUnit
|
||||||
|
|
||||||
|
let test1 () =
|
||||||
|
let empty = SplayMap.empty () in
|
||||||
|
let m = SplayMap.of_seq empty (Sequence.of_list [1, "1"; 2, "2"; 3, "3"]) in
|
||||||
|
OUnit.assert_equal ~printer:(fun s -> s) "2" (SplayMap.find m 2);
|
||||||
|
OUnit.assert_equal ~printer:(fun s -> s) "2" (SplayMap.find m 2);
|
||||||
|
OUnit.assert_equal ~printer:(fun s -> s) "3" (SplayMap.find m 3);
|
||||||
|
OUnit.assert_equal ~printer:(fun s -> s) "1" (SplayMap.find m 1);
|
||||||
|
OUnit.assert_raises Not_found (fun () -> SplayMap.find m 4);
|
||||||
|
()
|
||||||
|
|
||||||
|
let test_remove () =
|
||||||
|
let n = 100 in
|
||||||
|
let m = SplayMap.of_seq (SplayMap.empty ())
|
||||||
|
(Sequence.zip (Sequence.zip_i (Sequence.int_range ~start:0 ~stop:n))) in
|
||||||
|
for i = 0 to n do
|
||||||
|
OUnit.assert_equal ~printer:string_of_int i (SplayMap.find m i);
|
||||||
|
done;
|
||||||
|
let m = SplayMap.remove m (n/2) in
|
||||||
|
OUnit.assert_equal ~printer:string_of_int n (SplayMap.find m n);
|
||||||
|
OUnit.assert_raises Not_found (fun () -> SplayMap.find m (n/2));
|
||||||
|
()
|
||||||
|
|
||||||
|
let test_big () =
|
||||||
|
let n = 100_000 in
|
||||||
|
let m = SplayMap.of_seq (SplayMap.empty ())
|
||||||
|
(Sequence.zip (Sequence.zip_i (Sequence.int_range ~start:0 ~stop:n))) in
|
||||||
|
for i = 0 to n do
|
||||||
|
OUnit.assert_equal ~printer:string_of_int i (SplayMap.find m i);
|
||||||
|
done;
|
||||||
|
OUnit.assert_equal ~printer:string_of_int (n+1) (SplayMap.size m);
|
||||||
|
()
|
||||||
|
|
||||||
|
let suite =
|
||||||
|
"test_splayMap" >:::
|
||||||
|
[ "test1" >:: test1;
|
||||||
|
"test_remove" >:: test_remove;
|
||||||
|
"test_big" >:: test_big;
|
||||||
|
]
|
||||||
Loading…
Add table
Reference in a new issue