mirror of
https://github.com/c-cube/ocaml-containers.git
synced 2025-12-06 11:15:31 -05:00
refactored CCrandom (hide fuel, too complicated, but provide a fix operator);
bench_hash to compare hash combinators to the default hash function
This commit is contained in:
parent
e5a842829e
commit
2b15a21570
7 changed files with 253 additions and 161 deletions
2
.merlin
2
.merlin
|
|
@ -9,7 +9,7 @@ B _build/string
|
||||||
B _build/tests
|
B _build/tests
|
||||||
B _build/examples
|
B _build/examples
|
||||||
PKG oUnit
|
PKG oUnit
|
||||||
PKG bench
|
PKG benchmark
|
||||||
PKG threads
|
PKG threads
|
||||||
PKG threads.posix
|
PKG threads.posix
|
||||||
PKG lwt
|
PKG lwt
|
||||||
|
|
|
||||||
8
_oasis
8
_oasis
|
|
@ -146,6 +146,14 @@ Executable bench_batch
|
||||||
MainIs: bench_batch.ml
|
MainIs: bench_batch.ml
|
||||||
BuildDepends: containers,benchmark
|
BuildDepends: containers,benchmark
|
||||||
|
|
||||||
|
Executable bench_hash
|
||||||
|
Path: tests/
|
||||||
|
Install: false
|
||||||
|
CompiledObject: native
|
||||||
|
Build$: flag(bench) && flag(misc)
|
||||||
|
MainIs: bench_hash.ml
|
||||||
|
BuildDepends: containers,containers.misc
|
||||||
|
|
||||||
Executable test_levenshtein
|
Executable test_levenshtein
|
||||||
Path: tests/
|
Path: tests/
|
||||||
Install: false
|
Install: false
|
||||||
|
|
|
||||||
151
core/CCRandom.ml
151
core/CCRandom.ml
|
|
@ -41,112 +41,129 @@ let map f g st = f (g st)
|
||||||
|
|
||||||
let (>|=) g f st = map f g st
|
let (>|=) g f st = map f g st
|
||||||
|
|
||||||
let choose_array a st =
|
let _choose_array a st =
|
||||||
if Array.length a = 0 then invalid_arg "CCRandom.choose_array";
|
if Array.length a = 0 then invalid_arg "CCRandom.choose_array";
|
||||||
a.(Random.State.int st (Array.length a)) st
|
a.(Random.State.int st (Array.length a))
|
||||||
|
|
||||||
let choose l = choose_array (Array.of_list l)
|
let choose_array a st =
|
||||||
|
try Some (_choose_array a st st) with Invalid_argument _ -> None
|
||||||
|
|
||||||
let choose_return l = choose_array (Array.of_list (List.map return l))
|
let choose l =
|
||||||
|
let a = Array.of_list l in
|
||||||
|
choose_array a
|
||||||
|
|
||||||
(** {2 Fuel and Backtracking} *)
|
let choose_exn l =
|
||||||
|
let a = Array.of_list l in
|
||||||
|
fun st -> _choose_array a st st
|
||||||
|
|
||||||
module Fuel = struct
|
let choose_return l = _choose_array (Array.of_list l)
|
||||||
type fuel = int
|
|
||||||
|
|
||||||
exception Backtrack
|
let int i st = Random.State.int st i
|
||||||
|
|
||||||
(* consume [d] units of fuel and return [x] if it works *)
|
let small_int = int 100
|
||||||
let _consume d fuel x =
|
|
||||||
if fuel >= d then Some (fuel-d,x) else None
|
|
||||||
|
|
||||||
let _split i st =
|
let int_range i j st = i + Random.State.int st (j-i+1)
|
||||||
if i < 2 then raise Backtrack
|
|
||||||
|
let replicate n g st =
|
||||||
|
let rec aux acc n =
|
||||||
|
if n = 0 then acc else aux (g st :: acc) (n-1)
|
||||||
|
in aux [] n
|
||||||
|
|
||||||
|
exception SplitFail
|
||||||
|
|
||||||
|
let _split i st =
|
||||||
|
if i < 2 then raise SplitFail
|
||||||
else
|
else
|
||||||
let j = 1 + Random.State.int st (i-1) in
|
let j = 1 + Random.State.int st (i-1) in
|
||||||
(j, i-j)
|
(j, i-j)
|
||||||
|
|
||||||
let split i st = try Some (_split i st) with Backtrack -> None
|
let split i st = try Some (_split i st) with SplitFail -> None
|
||||||
|
|
||||||
(* partition of an int into [len] integers. We divide-and-conquer on
|
(* partition of an int into [len] integers. We divide-and-conquer on
|
||||||
the expected length, until it reaches 1. *)
|
the expected length, until it reaches 1. *)
|
||||||
let split_list i ~len st =
|
let split_list i ~len st =
|
||||||
let rec aux i ~len acc =
|
let rec aux i ~len acc =
|
||||||
if i < len then raise Backtrack
|
if i < len then raise SplitFail
|
||||||
else if len = 1 then i::acc
|
else if len = 1 then i::acc
|
||||||
else
|
else
|
||||||
(* split somewhere in the middle *)
|
(* split somewhere in the middle *)
|
||||||
let len1, len2 = _split len st in
|
let len1, len2 = _split len st in
|
||||||
|
assert (len = len1+len2);
|
||||||
if i = len
|
if i = len
|
||||||
then aux len1 ~len:len1 (aux len2 ~len:len2 acc)
|
then aux len1 ~len:len1 (aux len2 ~len:len2 acc)
|
||||||
else
|
else
|
||||||
let i1, i2 = _split (i-len1-len2) st in
|
let i1, i2 = _split (i-len) st in
|
||||||
aux i1 ~len:len1 (aux i2 ~len:len2 acc)
|
aux (i1+len1) ~len:len1 (aux (i2+len2) ~len:len2 acc)
|
||||||
in
|
in
|
||||||
try Some (aux i ~len []) with Backtrack -> None
|
try Some (aux i ~len []) with SplitFail -> None
|
||||||
|
|
||||||
(** {6 Fueled Generators} *)
|
let retry ?(max=10) g st =
|
||||||
|
|
||||||
type 'a t = fuel -> state -> (fuel * 'a) option
|
|
||||||
|
|
||||||
let return x fuel _st = _consume 1 fuel x
|
|
||||||
|
|
||||||
let return' fuel x fuel' _st = _consume fuel fuel' x
|
|
||||||
|
|
||||||
let flat_map f g fuel st =
|
|
||||||
match g fuel st with
|
|
||||||
| None -> None
|
|
||||||
| Some (fuel, x) -> f x fuel st
|
|
||||||
|
|
||||||
let (>>=) g f = flat_map f g
|
|
||||||
|
|
||||||
let map f g fuel st =
|
|
||||||
match g fuel st with
|
|
||||||
| None -> None
|
|
||||||
| Some (fuel, x) -> Some (fuel, f x)
|
|
||||||
|
|
||||||
let (>|=) g f = map f g
|
|
||||||
|
|
||||||
let consume fuel _st = _consume 1 fuel ()
|
|
||||||
|
|
||||||
let consume' fuel fuel' _st = _consume fuel fuel' ()
|
|
||||||
|
|
||||||
let fail _fuel _st = None
|
|
||||||
|
|
||||||
let retry ?(max=10) g fuel st =
|
|
||||||
let rec aux n =
|
let rec aux n =
|
||||||
match g fuel st with
|
match g st with
|
||||||
| None when n=0 -> None
|
| None when n=0 -> None
|
||||||
| None -> aux (n-1) (* retry *)
|
| None -> aux (n-1) (* retry *)
|
||||||
| Some _ as res -> res
|
| Some _ as res -> res
|
||||||
in
|
in
|
||||||
aux max
|
aux max
|
||||||
|
|
||||||
let rec try_successively l fuel st = match l with
|
let rec try_successively l st = match l with
|
||||||
| [] -> None
|
| [] -> None
|
||||||
| g :: l' ->
|
| g :: l' ->
|
||||||
begin match g fuel st with
|
begin match g st with
|
||||||
| None -> try_successively l' fuel st
|
| None -> try_successively l' st
|
||||||
| Some _ as res -> res
|
| Some _ as res -> res
|
||||||
end
|
end
|
||||||
|
|
||||||
let (<?>) a b = try_successively [a;b]
|
let (<?>) a b = try_successively [a;b]
|
||||||
|
|
||||||
let rec fix f fuel st = f (fix f) fuel st
|
exception Backtrack
|
||||||
|
|
||||||
let lift g fuel st = _consume 1 fuel (g st)
|
let _choose_array_call a f st =
|
||||||
|
try
|
||||||
|
f (_choose_array a st)
|
||||||
|
with Invalid_argument _ -> raise Backtrack
|
||||||
|
|
||||||
let lift' d g fuel st = _consume d fuel (g st)
|
let fix ?(sub1=[]) ?(sub2=[]) ?(subn=[]) ~base fuel st =
|
||||||
|
let sub1 = Array.of_list sub1
|
||||||
|
and sub2 = Array.of_list sub2
|
||||||
|
and subn = Array.of_list subn in
|
||||||
|
(* recursive function with fuel *)
|
||||||
|
let rec make fuel st =
|
||||||
|
if fuel=0 then raise Backtrack
|
||||||
|
else if fuel=1 then base st
|
||||||
|
else
|
||||||
|
_try_otherwise 0
|
||||||
|
[| _choose_array_call sub1 (fun f -> f (make (fuel-1)) st)
|
||||||
|
; _choose_array_call sub2
|
||||||
|
(fun f ->
|
||||||
|
match split fuel st with
|
||||||
|
| None -> raise Backtrack
|
||||||
|
| Some (i,j) -> f (make i) (make j) st
|
||||||
|
)
|
||||||
|
; _choose_array_call subn
|
||||||
|
(fun (len,f) ->
|
||||||
|
let len = len st in
|
||||||
|
match split_list fuel ~len st with
|
||||||
|
| None -> raise Backtrack
|
||||||
|
| Some l' ->
|
||||||
|
f (fun st -> List.map (fun x -> make x st) l') st
|
||||||
|
)
|
||||||
|
; base (* base case then *)
|
||||||
|
|]
|
||||||
|
and _try_otherwise i a =
|
||||||
|
if i=Array.length a then raise Backtrack
|
||||||
|
else try
|
||||||
|
a.(i) st
|
||||||
|
with Backtrack ->
|
||||||
|
_try_otherwise (i+1) a
|
||||||
|
in
|
||||||
|
make (fuel st) st
|
||||||
|
|
||||||
let run ?(fuel=fun st -> Random.State.int st 40) f st =
|
let pure x _st = x
|
||||||
match f (fuel st) st with
|
|
||||||
| None -> None
|
|
||||||
| Some (_fuel, x) -> Some x
|
|
||||||
|
|
||||||
exception GenFailure
|
let (<*>) f g st = f st (g st)
|
||||||
|
|
||||||
|
let __default_state = Random.State.make_self_init ()
|
||||||
|
|
||||||
|
let run ?(st=__default_state) g = g st
|
||||||
|
|
||||||
let run_exn ?fuel f st =
|
|
||||||
match run ?fuel f st with
|
|
||||||
| None -> raise GenFailure
|
|
||||||
| Some x -> x
|
|
||||||
end
|
|
||||||
|
|
|
||||||
|
|
@ -45,98 +45,72 @@ val map : ('a -> 'b) -> 'a t -> 'b t
|
||||||
|
|
||||||
val (>|=) : 'a t -> ('a -> 'b) -> 'b t
|
val (>|=) : 'a t -> ('a -> 'b) -> 'b t
|
||||||
|
|
||||||
val choose : 'a t list -> 'a t
|
val choose : 'a t list -> 'a option t
|
||||||
(** Choose a generator within the list.
|
(** Choose a generator within the list. *)
|
||||||
|
|
||||||
|
val choose_exn : 'a t list -> 'a t
|
||||||
|
(** Same as {!choose} but without option.
|
||||||
@raise Invalid_argument if the list is empty *)
|
@raise Invalid_argument if the list is empty *)
|
||||||
|
|
||||||
val choose_array : 'a t array -> 'a t
|
val choose_array : 'a t array -> 'a option t
|
||||||
|
|
||||||
val choose_return : 'a list -> 'a t
|
val choose_return : 'a list -> 'a t
|
||||||
(** Choose among the list
|
(** Choose among the list
|
||||||
@raise Invalid_argument if the list is empty *)
|
@raise Invalid_argument if the list is empty *)
|
||||||
|
|
||||||
(** {2 Fuel and Backtracking} *)
|
val replicate : int -> 'a t -> 'a list t
|
||||||
|
|
||||||
module Fuel : sig
|
val small_int : int t
|
||||||
type fuel = int
|
|
||||||
(** The fuel is a value that represents some "resource" used by the
|
|
||||||
random generator. *)
|
|
||||||
|
|
||||||
val split : fuel -> (fuel * fuel) option t
|
val int : int -> int t
|
||||||
(** Split a (fuel) value [n] into [n1,n2] where [n = n1 + n2].
|
|
||||||
|
val int_range : int -> int -> int t
|
||||||
|
(** Inclusive range *)
|
||||||
|
|
||||||
|
val split : int -> (int * int) option t
|
||||||
|
(** Split a positive value [n] into [n1,n2] where [n = n1 + n2].
|
||||||
@return [None] if the value is too small *)
|
@return [None] if the value is too small *)
|
||||||
|
|
||||||
val split_list : fuel -> len:int -> fuel list option t
|
val split_list : int -> len:int -> int list option t
|
||||||
(** Split a (fuel) value [n] into a list of values whose sum is [n]
|
(** Split a value [n] into a list of values whose sum is [n]
|
||||||
and whose length is [length].
|
and whose length is [length].
|
||||||
@return [None] if the value is too small *)
|
@return [None] if the value is too small *)
|
||||||
|
|
||||||
(** {6 Fueled Generators} *)
|
val retry : ?max:int -> 'a option t -> 'a option t
|
||||||
|
(** [retry g] calls [g] until it returns some value, or until the maximum
|
||||||
type 'a t = fuel -> state -> (fuel * 'a) option
|
|
||||||
(** Fueled generators use some fuel to generate a value.
|
|
||||||
Can fail by lack of fuel. *)
|
|
||||||
|
|
||||||
val return : 'a -> 'a t
|
|
||||||
(** [return x] is the generator that always returns [x], and consumes one
|
|
||||||
fuel doing it. *)
|
|
||||||
|
|
||||||
val return' : fuel -> 'a -> 'a t
|
|
||||||
(** [return' f x] returns [x] but also consumes [fuel]. *)
|
|
||||||
|
|
||||||
val flat_map : ('a -> 'b t) -> 'a t -> 'b t
|
|
||||||
|
|
||||||
val (>>=) : 'a t -> ('a -> 'b t) -> 'b t
|
|
||||||
|
|
||||||
val map : ('a -> 'b) -> 'a t -> 'b t
|
|
||||||
|
|
||||||
val (>|=) : 'a t -> ('a -> 'b) -> 'b t
|
|
||||||
|
|
||||||
val consume : unit t
|
|
||||||
(** Consume one fuel value *)
|
|
||||||
|
|
||||||
val consume' : fuel -> unit t
|
|
||||||
(** Consume the given amount of fuel *)
|
|
||||||
|
|
||||||
val fail : 'a t
|
|
||||||
(** Always fails. *)
|
|
||||||
|
|
||||||
val retry : ?max:int -> 'a t -> 'a t
|
|
||||||
(** [retry g] calls [g] until it returns some value, or until the maximum
|
|
||||||
number of retries was reached. If [g] fails,
|
number of retries was reached. If [g] fails,
|
||||||
then it counts for one iteration, and the generator retries.
|
then it counts for one iteration, and the generator retries.
|
||||||
@param max: maximum number of retries. Default [10] *)
|
@param max: maximum number of retries. Default [10] *)
|
||||||
|
|
||||||
val try_successively : 'a t list -> 'a t
|
val try_successively : 'a option t list -> 'a option t
|
||||||
(** [try_successively l] tries each generator of [l], one after the other.
|
(** [try_successively l] tries each generator of [l], one after the other.
|
||||||
If some generator succeeds its result is returned, else the
|
If some generator succeeds its result is returned, else the
|
||||||
next generator is tried *)
|
next generator is tried *)
|
||||||
|
|
||||||
val (<?>) : 'a t -> 'a t -> 'a t
|
val (<?>) : 'a option t -> 'a option t -> 'a option t
|
||||||
(** [a <?> b] is a choice operator. It first tries [a], and returns its
|
(** [a <?> b] is a choice operator. It first tries [a], and returns its
|
||||||
result if successful. If [a] fails, then [b] is returned. *)
|
result if successful. If [a] fails, then [b] is returned. *)
|
||||||
|
|
||||||
val fix : ('a t -> 'a t) -> 'a t
|
val fix :
|
||||||
(** Recursion combinators, for building (fueled) recursive values *)
|
?sub1:('a t -> 'a t) list ->
|
||||||
|
?sub2:('a t -> 'a t -> 'a t) list ->
|
||||||
|
?subn:(int t * ('a list t -> 'a t)) list ->
|
||||||
|
base:'a t -> int t -> 'a t
|
||||||
|
(** Recursion combinators, for building recursive values.
|
||||||
|
The integer generator is used to provide fuel. The [sub_] generators
|
||||||
|
should use their arguments only once!
|
||||||
|
@param sub1 cases that recurse on one value
|
||||||
|
@param sub2 cases that use the recursive gen twice
|
||||||
|
@param subn cases that use a list of recursive cases *)
|
||||||
|
|
||||||
val lift : 'a random_gen -> 'a t
|
(** {6 Applicative} *)
|
||||||
(** lifts a regular random generator into a fueled one, that consumes
|
|
||||||
one fuel unit *)
|
|
||||||
|
|
||||||
val lift' : fuel -> 'a random_gen -> 'a t
|
val pure : 'a -> 'a t
|
||||||
(** lifts a regular random generator into a fueled one, that consumes
|
|
||||||
one fuel unit *)
|
|
||||||
|
|
||||||
(** {6 Running} *)
|
val (<*>) : ('a -> 'b) t -> 'a t -> 'b t
|
||||||
|
|
||||||
val run : ?fuel:fuel random_gen -> 'a t -> 'a option random_gen
|
(** {6 Run a generator} *)
|
||||||
(** Run the given fueled generator with an amount of fuel
|
|
||||||
given by the [fuel] generator.
|
|
||||||
@return None if the *)
|
|
||||||
|
|
||||||
exception GenFailure
|
val run : ?st:state -> 'a t -> 'a
|
||||||
|
(** Using a random state (possibly the one in argument) run a generator *)
|
||||||
|
|
||||||
val run_exn : ?fuel:fuel random_gen -> 'a t -> 'a random_gen
|
|
||||||
(** Same as {!run}, but in case of failure it raises an exception.
|
|
||||||
@raise GenFailure in case the generator fails *)
|
|
||||||
end
|
|
||||||
|
|
|
||||||
6
misc/.merlin
Normal file
6
misc/.merlin
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
REC
|
||||||
|
S ../core
|
||||||
|
S .
|
||||||
|
B ../_build/core/
|
||||||
|
B ../_build/misc/
|
||||||
|
PKG core
|
||||||
3
tests/.merlin
Normal file
3
tests/.merlin
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
S .
|
||||||
|
B ../_build/tests/
|
||||||
|
REC
|
||||||
84
tests/bench_hash.ml
Normal file
84
tests/bench_hash.ml
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
(** Test hash functions *)
|
||||||
|
|
||||||
|
type tree =
|
||||||
|
| Empty
|
||||||
|
| Node of int * tree list
|
||||||
|
|
||||||
|
let mk_node i l = Node (i,l)
|
||||||
|
|
||||||
|
let random_tree =
|
||||||
|
CCRandom.(fix
|
||||||
|
~base:(return Empty)
|
||||||
|
~subn:[int 10, (fun sublist -> pure mk_node <*> small_int <*> sublist)]
|
||||||
|
(int_range 15 150)
|
||||||
|
)
|
||||||
|
|
||||||
|
let random_list =
|
||||||
|
CCRandom.(
|
||||||
|
int 5 >>= fun len ->
|
||||||
|
CCList.random_len len random_tree
|
||||||
|
)
|
||||||
|
|
||||||
|
let rec eq t1 t2 = match t1, t2 with
|
||||||
|
| Empty, Empty -> true
|
||||||
|
| Node(i1,l1), Node (i2,l2) -> i1=i2 && CCList.equal eq l1 l2
|
||||||
|
| Node _, _
|
||||||
|
| _, Node _ -> false
|
||||||
|
|
||||||
|
let rec hash_tree t h = match t with
|
||||||
|
| Empty -> CCHash.string_ "empty" h
|
||||||
|
| Node (i, l) ->
|
||||||
|
h |> CCHash.string_ "node" |> CCHash.int_ i |> CCHash.list_ hash_tree l
|
||||||
|
|
||||||
|
module Box = Containers_misc.PrintBox
|
||||||
|
|
||||||
|
let tree2box = Box.mk_tree
|
||||||
|
(function
|
||||||
|
| Empty -> Box.empty, []
|
||||||
|
| Node (i,l) -> Box.line (CCPrint.sprintf "node %d" i), l
|
||||||
|
)
|
||||||
|
|
||||||
|
let l = CCRandom.(run (CCList.random random_list))
|
||||||
|
|
||||||
|
let pp_list buf l =
|
||||||
|
let box = Box.(frame (vlist ~bars:true (List.map tree2box l))) in
|
||||||
|
CCPrint.string buf (Box.to_string box)
|
||||||
|
|
||||||
|
(* print some terms *)
|
||||||
|
let () =
|
||||||
|
List.iter
|
||||||
|
(fun l -> CCPrint.printf "%a\n" pp_list l) l
|
||||||
|
|
||||||
|
|
||||||
|
module H = Hashtbl.Make(struct
|
||||||
|
type t = tree
|
||||||
|
let equal = eq
|
||||||
|
let hash = CCHash.apply hash_tree
|
||||||
|
end)
|
||||||
|
|
||||||
|
let print_hashcons_stats st =
|
||||||
|
let open Hashtbl in
|
||||||
|
CCPrint.printf
|
||||||
|
"tbl stats: %d elements, num buckets: %d, max bucket: %d\n"
|
||||||
|
st.num_bindings st.num_buckets st.max_bucket_length;
|
||||||
|
Array.iteri
|
||||||
|
(fun i n -> CCPrint.printf " %d\t buckets have length %d\n" n i)
|
||||||
|
st.bucket_histogram
|
||||||
|
|
||||||
|
let () =
|
||||||
|
let st = Random.State.make_self_init () in
|
||||||
|
let n = 50_000 in
|
||||||
|
CCPrint.printf "generate %d elements...\n" n;
|
||||||
|
let l = CCRandom.run ~st (CCList.random_len n random_tree) in
|
||||||
|
(* with custom hashtable *)
|
||||||
|
CCPrint.printf "### custom hashtable\n";
|
||||||
|
let tbl = H.create 256 in
|
||||||
|
List.iter (fun t -> H.replace tbl t ()) l;
|
||||||
|
print_hashcons_stats (H.stats tbl);
|
||||||
|
(* with default hashtable *)
|
||||||
|
CCPrint.printf "### default hashtable\n";
|
||||||
|
let tbl' = Hashtbl.create 256 in
|
||||||
|
List.iter (fun t -> Hashtbl.replace tbl' t ()) l;
|
||||||
|
print_hashcons_stats (Hashtbl.stats tbl');
|
||||||
|
()
|
||||||
|
|
||||||
Loading…
Add table
Reference in a new issue