diff --git a/AUTHORS.adoc b/AUTHORS.adoc index be70282a..de89cd30 100644 --- a/AUTHORS.adoc +++ b/AUTHORS.adoc @@ -12,5 +12,7 @@ - Emmanuel Surleau (emm) - Guillaume Bury (guigui) - JP Rodi -- octachron (Florian Angeletti) +- Florian Angeletti (@octachron) - Johannes Kloos +- Geoff Gole (@gsg) +- Roma Sokolov (@little-arhat) diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index 2f2ab89f..506ae9df 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -1,5 +1,19 @@ = Changelog +== 0.20 + +- bugfix in `CCArray.equal` +- fix `CCString.*_ascii`; add `CCChar.{upper,lower}case_ascii` + +- add functions in `CCArray`: fold2,iter2,map2 +- add `CCArray.rev` +- add `CCFloat.round` +- add `CCVector.append_gen` +- add `CCList.{head_opt,last_opt}` +- add `CCInt.{print_binary,to_string_binary}` + tests (thanks @gsg) +- more general types for `CCArray.{for_all2,exists2}` +- more general type for `CCResult.map_or` + == 0.19 - add regression test for #75 diff --git a/Makefile b/Makefile index f6b06ea1..8a5d5b43 100644 --- a/Makefile +++ b/Makefile @@ -50,6 +50,13 @@ examples: all push_doc: doc rsync -tavu containers.docdir/* cedeela.fr:~/simon/root/software/containers/ +push_doc_gh: doc + git checkout gh-pages && \ + rm -rf dev/ && \ + mkdir -p dev && \ + cp -r containers.docdir/* dev/ && \ + git add --all dev + DONTTEST=myocamlbuild.ml setup.ml $(wildcard src/**/*.cppo.*) QTESTABLE=$(filter-out $(DONTTEST), \ $(wildcard src/core/*.ml) \ diff --git a/README.adoc b/README.adoc index b0180351..5028ce5a 100644 --- a/README.adoc +++ b/README.adoc @@ -3,7 +3,7 @@ :source-highlighter: pygments What is _containers_? (take a look at the link:TUTORIAL.adoc[tutorial]! -or the http://cedeela.fr/~simon/software/containers[documentation]) +or the http://cedeela.fr/~simon/software/containers[current documentation]) In `containers` and `containers.data`, all modules abide by _pay for what you use_: only modules that are used are linked (there are no cross-module dependencies). @@ -230,6 +230,18 @@ The library has moved to https://github.com/c-cube/containers-misc . `containers.lwt` has moved to https://github.com/c-cube/containers-lwt . [[build]] + +== Documentation + +In general, see http://c-cube.github.io/ocaml-containers/ or +http://cedeela.fr/~simon/software/containers + +by version: + +- http://c-cube.github.io/ocaml-containers/dev/[dev branch] +- http://c-cube.github.io/ocaml-containers/0.17/[0.17] +- http://c-cube.github.io/ocaml-containers/0.19/[0.19] + == Build You will need OCaml `>=` 4.00.0. @@ -270,7 +282,3 @@ A few guidelines: - add tests if possible (using `qtest`). Powered by image:http://oasis.forge.ocamlcore.org/oasis-badge.png[alt="OASIS", style="border: none;", link="http://oasis.forge.ocamlcore.org/"] - -== Documentation by version - -- http://c-cube.github.io/ocaml-containers/0.17/[0.17] diff --git a/_oasis b/_oasis index 5c1d47ee..1fbf5c55 100644 --- a/_oasis +++ b/_oasis @@ -1,6 +1,6 @@ OASISFormat: 0.4 Name: containers -Version: 0.19 +Version: 0.20 Homepage: https://github.com/c-cube/ocaml-containers Authors: Simon Cruanes License: BSD-2-clause @@ -179,7 +179,7 @@ Executable run_qtest containers.io, containers.advanced, containers.sexp, containers.bigarray, containers.unix, containers.thread, containers.data, - sequence, gen, unix, oUnit, qcheck + sequence, gen, unix, oUnit, qcheck Test all Command: ./run_qtest.native diff --git a/src/core/CCArray.ml b/src/core/CCArray.ml index a7230a77..cc7b622f 100644 --- a/src/core/CCArray.ml +++ b/src/core/CCArray.ml @@ -94,18 +94,30 @@ module type S = sig val for_all : ('a -> bool) -> 'a t -> bool - val for_all2 : ('a -> 'a -> bool) -> 'a t -> 'a t -> bool + val for_all2 : ('a -> 'b -> bool) -> 'a t -> 'b t -> bool (** Forall on pairs of arrays. - @raise Invalid_argument if they have distinct lengths *) + @raise Invalid_argument if they have distinct lengths + allow different types @since 0.20 *) val exists : ('a -> bool) -> 'a t -> bool - val exists2 : ('a -> 'a -> bool) -> 'a t -> 'a t -> bool + val exists2 : ('a -> 'b -> bool) -> 'a t -> 'b t -> bool (** Exists on pairs of arrays. - @raise Invalid_argument if they have distinct lengths *) + @raise Invalid_argument if they have distinct lengths + allow different types @since 0.20 *) + + val fold2 : ('acc -> 'a -> 'b -> 'acc) -> 'acc -> 'a t -> 'b t -> 'acc + (** Fold on two arrays stepwise. + @raise Invalid_argument if they have distinct lengths + @since 0.20 *) + + val iter2 : ('a -> 'b -> unit) -> 'a t -> 'b t -> unit + (** Iterate on two arrays stepwise. + @raise Invalid_argument if they have distinct lengths + @since 0.20 *) val shuffle : 'a t -> unit - (** shuffle randomly the array, in place *) + (** Shuffle randomly the array, in place *) val shuffle_with : Random.State.t -> 'a t -> unit (** Like shuffle but using a specialized random state *) @@ -122,15 +134,15 @@ module type S = sig val pp: ?sep:string -> (Buffer.t -> 'a -> unit) -> Buffer.t -> 'a t -> unit - (** print an array of items with printing function *) + (** Print an array of items with printing function *) val pp_i: ?sep:string -> (Buffer.t -> int -> 'a -> unit) -> Buffer.t -> 'a t -> unit - (** print an array, giving the printing function both index and item *) + (** Print an array, giving the printing function both index and item *) val print : ?sep:string -> (Format.formatter -> 'a -> unit) -> Format.formatter -> 'a t -> unit - (** print an array of items with printing function *) + (** Print an array of items with printing function *) end (** {2 General Implementation} @@ -150,10 +162,10 @@ let _reverse_in_place a i ~len = done let rec _equal eq a1 i1 j1 a2 i2 j2 = - if i1 = j1 || i2 = j2 + if i1 = j1 then (assert (i1=j1 && i2=j2); true) else - eq a1.(i1) a2.(i2) && _equal eq a1 (i1+1) j1 a2 (i2+2) j2 + eq a1.(i1) a2.(i2) && _equal eq a1 (i1+1) j1 a2 (i2+1) j2 let rec _compare cmp a1 i1 j1 a2 i2 j2 = if i1 = j1 @@ -291,6 +303,10 @@ let empty = [| |] let map = Array.map +let map2 f a b = + if Array.length a <> Array.length b then invalid_arg "map2"; + Array.init (Array.length a) (fun i -> f (Array.unsafe_get a i) (Array.unsafe_get b i)) + let length = Array.length let get = Array.get @@ -350,6 +366,21 @@ let reverse_in_place a = a = [| 6;5;4;3;2;1 |] *) +let rev a = + let b = Array.copy a in + reverse_in_place b; + b + +(*$Q + Q.(array small_int) (fun a -> rev (rev a) = a) +*) + +(*$T + rev [| 1; 2; 3 |] = [| 3; 2; 1 |] + rev [| 1; 2; |] = [| 2; 1 |] + rev [| |] = [| |] + *) + let find f a = _find (fun _ -> f ) a 0 (Array.length a) @@ -455,6 +486,28 @@ let for_all2 p a b = let exists2 p a b = _exists2 p a b 0 0 ~len:(min (Array.length a) (Array.length b)) +let _iter2 f a b i j ~len = + for o = 0 to len-1 do + f (Array.get a (i+o)) (Array.get b (j+o)) + done + +let _fold2 f acc a b i j ~len = + let rec aux acc o = + if o=len then acc + else + let acc = f acc (Array.get a (i+o)) (Array.get b (j+o)) in + aux acc (o+1) + in + aux acc 0 + +let iter2 f a b = + if length a <> length b then invalid_arg "iter2"; + _iter2 f a b 0 0 ~len:(Array.length a) + +let fold2 f acc a b = + if length a <> length b then invalid_arg "fold2"; + _fold2 f acc a b 0 0 ~len:(Array.length a) + let (--) i j = if i<=j then @@ -495,6 +548,15 @@ let equal eq a b = && _equal eq a 0 (Array.length a) b 0 (Array.length b) +(*$Q + Q.(pair (array small_int)(array small_int)) (fun (a,b) -> \ + equal (=) a b = equal (=) b a) +*) + +(*$T + equal (=) [|1|] [|1|] +*) + let compare cmp a b = _compare cmp a 0 (Array.length a) b 0 (Array.length b) @@ -658,6 +720,14 @@ module Sub = struct Sub.exists2 (=) (Sub.make [| 1;2;3;4 |] 1 ~len:2) (Sub.make [| 0;1;3;4 |] 1 ~len:3) *) + let iter2 f a b = + if length a <> length b then invalid_arg "iter2"; + _iter2 f a.arr b.arr a.i b.i ~len:(length a) + + let fold2 f acc a b = + if length a <> length b then invalid_arg "fold2"; + _fold2 f acc a.arr b.arr a.i b.i ~len:(length a) + let shuffle a = _shuffle Random.int a.arr a.i a.j diff --git a/src/core/CCArray.mli b/src/core/CCArray.mli index eb6b9d29..8206d2cb 100644 --- a/src/core/CCArray.mli +++ b/src/core/CCArray.mli @@ -96,15 +96,27 @@ module type S = sig val for_all : ('a -> bool) -> 'a t -> bool - val for_all2 : ('a -> 'a -> bool) -> 'a t -> 'a t -> bool + val for_all2 : ('a -> 'b -> bool) -> 'a t -> 'b t -> bool (** Forall on pairs of arrays. - @raise Invalid_argument if they have distinct lengths *) + @raise Invalid_argument if they have distinct lengths + allow different types @since 0.20 *) val exists : ('a -> bool) -> 'a t -> bool - val exists2 : ('a -> 'a -> bool) -> 'a t -> 'a t -> bool + val exists2 : ('a -> 'b -> bool) -> 'a t -> 'b t -> bool (** Exists on pairs of arrays. - @raise Invalid_argument if they have distinct lengths *) + @raise Invalid_argument if they have distinct lengths + allow different types @since 0.20 *) + + val fold2 : ('acc -> 'a -> 'b -> 'acc) -> 'acc -> 'a t -> 'b t -> 'acc + (** Fold on two arrays stepwise. + @raise Invalid_argument if they have distinct lengths + @since 0.20 *) + + val iter2 : ('a -> 'b -> unit) -> 'a t -> 'b t -> unit + (** Iterate on two arrays stepwise. + @raise Invalid_argument if they have distinct lengths + @since 0.20 *) val shuffle : 'a t -> unit (** Shuffle randomly the array, in place *) @@ -143,6 +155,15 @@ include S with type 'a t := 'a t val map : ('a -> 'b) -> 'a t -> 'b t +val map2 : ('a -> 'b -> 'c) -> 'a t -> 'b t -> 'c t +(** Map on two arrays stepwise. + @raise Invalid_argument if they have distinct lengths + @since 0.20 *) + +val rev : 'a t -> 'a t +(** Copy + reverse in place + @since 0.20 *) + val filter : ('a -> bool) -> 'a t -> 'a t (** Filter elements out of the array. Only the elements satisfying the given predicate will be kept. *) diff --git a/src/core/CCChar.ml b/src/core/CCChar.ml index a1622930..460d2eed 100644 --- a/src/core/CCChar.ml +++ b/src/core/CCChar.ml @@ -11,3 +11,13 @@ let compare = Char.compare let pp = Buffer.add_char let print = Format.pp_print_char + +let lowercase_ascii c = + if c >= 'A' && c <= 'Z' + then Char.unsafe_chr (Char. code c + 32) + else c + +let uppercase_ascii c = + if c >= 'a' && c <= 'z' + then Char.unsafe_chr (Char.code c - 32) + else c diff --git a/src/core/CCChar.mli b/src/core/CCChar.mli index 1ca8dcd3..748ab659 100644 --- a/src/core/CCChar.mli +++ b/src/core/CCChar.mli @@ -9,5 +9,13 @@ type t = char val equal : t -> t -> bool val compare : t -> t -> int +val lowercase_ascii : t -> t +(** See {!Char} + @since 0.20 *) + +val uppercase_ascii : t -> t +(** See {!Char} + @since 0.20 *) + val pp : Buffer.t -> t -> unit val print : Format.formatter -> t -> unit diff --git a/src/core/CCFloat.ml b/src/core/CCFloat.ml index 2fbbe071..0d73342d 100644 --- a/src/core/CCFloat.ml +++ b/src/core/CCFloat.ml @@ -60,6 +60,17 @@ let sign_exn (a:float) = if is_nan a then raise (TrapNaN "sign_exn") else compare a 0. +let round x = + let low = floor x in + let high = ceil x in + if x-.low > high-.x then high else low + +(*$= + 2. (round 1.6) + 1. (round 1.4) + 0. (round 0.) +*) + let to_int (a:float) = Pervasives.int_of_float a let of_int (a:int) = Pervasives.float_of_int a diff --git a/src/core/CCFloat.mli b/src/core/CCFloat.mli index 5b47483b..766027ac 100644 --- a/src/core/CCFloat.mli +++ b/src/core/CCFloat.mli @@ -19,7 +19,7 @@ val min_value : t val max_finite_value : t -val epsilon : float +val epsilon : t val is_nan : t -> bool @@ -39,7 +39,7 @@ val max : t -> t -> t val equal : t -> t -> bool -val compare : float -> float -> int +val compare : t -> t -> int type 'a printer = Buffer.t -> 'a -> unit type 'a formatter = Format.formatter -> 'a -> unit @@ -54,10 +54,14 @@ val random : t -> t random_gen val random_small : t random_gen val random_range : t -> t -> t random_gen -val fsign : t -> float +val fsign : t -> t (** [fsign x] is one of [-1., -0., +0., +1.], or [nan] if [x] is NaN. @since 0.7 *) +val round : t -> t +(** [round f] returns the closest integer value, either above or below + @since 0.20 *) + exception TrapNaN of string val sign_exn : t -> int (** [sign_exn x] will return the sign of [x] as [1, 0] or [-1], or raise an @@ -75,7 +79,7 @@ val of_string : string -> t val equal_precision : epsilon:t -> t -> t -> bool (** Equality with allowed error up to a non negative epsilon value *) -val classify : float -> fpclass +val classify : t -> fpclass (** {2 Infix Operators} diff --git a/src/core/CCInt.ml b/src/core/CCInt.ml index ba1d82a2..16012426 100644 --- a/src/core/CCInt.ml +++ b/src/core/CCInt.ml @@ -48,12 +48,56 @@ let random_range i j st = i + random (j-i) st let pp buf = Printf.bprintf buf "%d" let print fmt = Format.pp_print_int fmt +let most_significant_bit = + (-1) lxor ((-1) lsr 1) + let to_string = string_of_int let of_string s = try Some (int_of_string s) with _ -> None +type output = char -> unit + +(* abstract printer *) +let to_binary_gen (out:output) n = + let n = if n<0 then (out '-'; -n) else n in + out '0'; out 'b'; + let rec loop started bit n = + if bit = 0 then ( + if not started then out '0' + ) else ( + let b = n land bit in + if b = 0 then ( + if started then out '0'; + loop started (bit lsr 1) n + ) else ( + out '1'; + loop true (bit lsr 1) n + ) + ) + in + loop false most_significant_bit n + +let print_binary out n = + to_binary_gen (Format.pp_print_char out) n + +let to_string_binary n = + let buf = Buffer.create 16 in + to_binary_gen (Buffer.add_char buf) n; + Buffer.contents buf + +(*$= & ~printer:CCFun.id + "0b111" (to_string_binary 7) + "-0b111" (to_string_binary (-7)) + "0b0" (to_string_binary 0) +*) + + +(*$Q & ~count:10_000 + Q.int (fun n -> n = int_of_string (to_string_binary n)) +*) + module Infix = struct let (=) = Pervasives.(=) let (<>) = Pervasives.(<>) diff --git a/src/core/CCInt.mli b/src/core/CCInt.mli index adc77339..7657a315 100644 --- a/src/core/CCInt.mli +++ b/src/core/CCInt.mli @@ -40,6 +40,13 @@ val to_string : t -> string val of_string : string -> t option (** @since 0.13 *) +val print_binary : t formatter +(** prints as "0b00101010". + @since 0.20 *) + +val to_string_binary : t -> string +(** @since 0.20 *) + val min : t -> t -> t (** @since 0.17 *) diff --git a/src/core/CCList.ml b/src/core/CCList.ml index 92c2747d..17a29a04 100644 --- a/src/core/CCList.ml +++ b/src/core/CCList.ml @@ -569,6 +569,24 @@ let last n l = let len = List.length l in if len < n then l else drop (len-n) l +let head_opt = function + | [] -> None + | x::_ -> Some x + +let rec last_opt = function + | [] -> None + | [x] -> Some x + | _ :: tail -> last_opt tail + +(*$= & ~printer:Q.Print.(option int) + (Some 1) (head_opt [1;2;3]) + (Some 1) (head_opt [1]) + None (head_opt []) + (Some 3) (last_opt [1;2;3]) + (Some 1) (last_opt [1]) + None (last_opt []) +*) + let rec find_pred p l = match l with | [] -> None | x :: _ when p x -> Some x diff --git a/src/core/CCList.mli b/src/core/CCList.mli index a3a35b26..93df4694 100644 --- a/src/core/CCList.mli +++ b/src/core/CCList.mli @@ -134,6 +134,14 @@ val last : int -> 'a t -> 'a t (** [last n l] takes the last [n] elements of [l] (or less if [l] doesn't have that many elements *) +val head_opt : 'a t -> 'a option +(** First element. + @since 0.20 *) + +val last_opt : 'a t -> 'a option +(** Last element. + @since 0.20 *) + val find_pred : ('a -> bool) -> 'a t -> 'a option (** [find_pred p l] finds the first element of [l] that satisfies [p], or returns [None] if no element satisfies [p] diff --git a/src/core/CCResult.mli b/src/core/CCResult.mli index 9a1dedc2..fb764167 100644 --- a/src/core/CCResult.mli +++ b/src/core/CCResult.mli @@ -67,7 +67,7 @@ val get_exn : ('a, _) t -> 'a val get_or : ('a, _) t -> default:'a -> 'a (** [get_or e ~default] returns [x] if [e = Ok x], [default] otherwise *) -val map_or : ('a -> 'b) -> ('a, 'b) t -> default:'b -> 'b +val map_or : ('a -> 'b) -> ('a, 'c) t -> default:'b -> 'b (** [map_or f e ~default] returns [f x] if [e = Ok x], [default] otherwise *) val catch : ('a, 'err) t -> ok:('a -> 'b) -> err:('err -> 'b) -> 'b diff --git a/src/core/CCString.cppo.ml b/src/core/CCString.cppo.ml index d29da1e6..61fa8240 100644 --- a/src/core/CCString.cppo.ml +++ b/src/core/CCString.cppo.ml @@ -651,10 +651,20 @@ let lowercase_ascii = String.lowercase_ascii #else -let capitalize_ascii = String.capitalize -let uncapitalize_ascii = String.uncapitalize -let uppercase_ascii = String.uppercase -let lowercase_ascii = String.lowercase +let capitalize_ascii s = + mapi + (fun i c -> if i=0 then CCChar.uppercase_ascii c else c) + s + + +let uncapitalize_ascii s = + mapi + (fun i c -> if i=0 then CCChar.lowercase_ascii c else c) + s + +let uppercase_ascii = map CCChar.uppercase_ascii + +let lowercase_ascii = map CCChar.lowercase_ascii #endif diff --git a/src/core/CCVector.ml b/src/core/CCVector.ml index 40296609..240774fe 100644 --- a/src/core/CCVector.ml +++ b/src/core/CCVector.ml @@ -226,6 +226,20 @@ let append_list a b = match b with length v = List.length l1 + List.length l2) *) +let rec append_gen a b = match b() with + | None -> () + | Some x -> push a x; append_gen a b + +(*$Q + Q.(pair (list int)(list int)) (fun (l1,l2) -> \ + let v = of_list l1 in append_gen v (Gen.of_list l2); \ + to_list v = (l1 @ l2)) + Q.(pair (list int)(list int)) (fun (l1,l2) -> \ + let v = of_list l1 in append_gen v (Gen.of_list l2); \ + length v = List.length l1 + List.length l2) +*) + + (*$inject let gen x = let small = length in diff --git a/src/core/CCVector.mli b/src/core/CCVector.mli index e3a329cd..ad47b070 100644 --- a/src/core/CCVector.mli +++ b/src/core/CCVector.mli @@ -84,6 +84,10 @@ val append_list : ('a, rw) t -> 'a list -> unit (** Append content of list @since 0.14 *) +val append_gen : ('a, rw) t -> 'a gen -> unit +(** Append content of generator + @since 0.20 *) + val equal : 'a equal -> ('a,_) t equal val compare : 'a ord -> ('a,_) t ord