diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index 85ecba8e..c0114d68 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -1,5 +1,68 @@ = Changelog +== 1.0 + +See https://github.com/c-cube/ocaml-containers/issues/84 for an overview. + +**Breaking and Removals**: + +- simplify and cleanup of CCGraph +- remove poly-variant based errors, use `result` everywhere +- remove deprecated functions and modules +- remove `CCVHashconsedSet` +- remove `CCAllocCache` +- remove `CCBloom` +- update benchmarks (ignoring hamt); remove useless old script +- simplify `CCHash`, changing the type to `'a -> int`, relying on + `Hashtbl.seeded_hash` for combining hashes +- split `CCList.Zipper` into its own module, `CCZipper` in containers.data +- change argument ordering in `CCList.Assoc` +- remove `CCList.Idx`, rename its functions to toplevel +- remove `CCList.Set`, move functions to toplevel and rename them +- rewrite `CCBitField` with a much simpler interface +- split `CCArray.Sub` into `CCArray_slice` +- remove containers.string + * remove CCParse and CCKMP (will be replaced in core) +- `CCFormat`: + * remove `start/stop` args, make `sep` a `unit printer` + * many other improvements and additions + * add `CCFormat.tee` + * add `CCFormat.Dump.{result,to_string}` +- replace `or_` by `default` in labelled functions +- remove trailing `_` in `CCOrd` primitives +- remove `containers.io` (deprecated for a while) +- remove `containers.bigarray` +- remove `CCSexpM`, use ocamllex for a much simpler `CCSexp` using ocamllex +- add `CCParse` into core, a simple, lightweight version of parser combs +- remove `CCPrint`, use `CCFormat` instead (also, update tests relying on it) +- remove containers.advanced +- change type of `CCUnix.escape_str` + +**Additions**: + +- `CCHashtbl`: + * `CCHash.{list,array}_comm` + * `CCHashtbl.Poly` and fix issue in Containers (close #46) + * `CCHashtbl.get_or_add` +- `CCList.sublists_of_len` (close #97) +- `Char.{of_int{,_exn},to_int}` (close #95) +- Add `CCResult.{is_ok,is_error}` +- improve `CCUnix` a bit +- update `containers.ml` so as to include all core containers +- add `CCOrd.Infix` +- use `Labels` versions of `CCList` and `CCArray` +- add `CCString.edit_distance` +- expose `CCString.Find` for efficient sub-string searching + +**Bugfixes**: + +- `CCIO`: deal properly with broken symlinks and permission errors +- test for #94 (using Thread.yield to trigger segfault) + Fix `CCSemaphore.with_acquire`: release a non locked mutex is UB +- containers.top: remove printers on structural types (#71) +- add doc for `of_list` in relevant modules (close #85) +- bugfix: do not use `Sequence.flatMap` (close #90) + == 0.22 - threads/CCLock: add `try_with_lock` to wrap `Mutex.try_lock` diff --git a/_oasis b/_oasis index 60512027..4ae672b6 100644 --- a/_oasis +++ b/_oasis @@ -1,6 +1,6 @@ OASISFormat: 0.4 Name: containers -Version: 0.22 +Version: 1.0 Homepage: https://github.com/c-cube/ocaml-containers Authors: Simon Cruanes License: BSD-2-clause diff --git a/src/core/CCArray.mli b/src/core/CCArray.mli index 39de56e6..611d7b9f 100644 --- a/src/core/CCArray.mli +++ b/src/core/CCArray.mli @@ -54,14 +54,14 @@ val reverse_in_place : 'a t -> unit val sorted : ('a -> 'a -> int) -> 'a t -> 'a array (** [sorted cmp a] makes a copy of [a] and sorts it with [cmp]. - @since NEXT_RELEASE *) + @since 1.0 *) val sort_indices : ('a -> 'a -> int) -> 'a t -> int array (** [sort_indices cmp a] returns a new array [b], with the same length as [a], such that [b.(i)] is the index of the [i]-th element of [a] in [sort cmp a]. In other words, [map (fun i -> a.(i)) (sort_indices a) = sorted cmp a]. [a] is not modified. - @since NEXT_RELEASE *) + @since 1.0 *) val sort_ranking : ('a -> 'a -> int) -> 'a t -> int array (** [sort_ranking cmp a] returns a new array [b], with the same length as [a], @@ -73,7 +73,7 @@ val sort_ranking : ('a -> 'a -> int) -> 'a t -> int array Without duplicates, we also have [lookup_exn a.(i) (sorted a) = (sorted_ranking a).(i)] - @since NEXT_RELEASE *) + @since 1.0 *) val find : ('a -> 'b option) -> 'a t -> 'b option (** [find f a] returns [Some y] if there is an element [x] such diff --git a/src/core/CCArrayLabels.ml b/src/core/CCArrayLabels.ml deleted file mode 120000 index 607a4922..00000000 --- a/src/core/CCArrayLabels.ml +++ /dev/null @@ -1 +0,0 @@ -CCArray.ml \ No newline at end of file diff --git a/src/core/CCArrayLabels.ml b/src/core/CCArrayLabels.ml new file mode 100644 index 00000000..85e152c8 --- /dev/null +++ b/src/core/CCArrayLabels.ml @@ -0,0 +1,617 @@ + +(* This file is free software, part of containers. See file "license" for more details. *) + +(** {1 Array utils} *) + +type 'a sequence = ('a -> unit) -> unit +type 'a klist = unit -> [`Nil | `Cons of 'a * 'a klist] +type 'a gen = unit -> 'a option +type 'a equal = 'a -> 'a -> bool +type 'a ord = 'a -> 'a -> int +type 'a random_gen = Random.State.t -> 'a +type 'a printer = Format.formatter -> 'a -> unit + +(*$T + let st = Random.State.make [||] in let a = 0--10000 in \ + let b = Array.copy a in shuffle_with st a; a <> b +*) + +(** {2 Arrays} *) + +type 'a t = 'a array + +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 + +let get_safe a i = + if i>=0 && i acc + | `Continue -> fold_while_i f acc (i+1) + else acc + in fold_while_i f acc 0 + +(*$T + fold_while (fun acc b -> if b then acc+1, `Continue else acc, `Stop) 0 (Array.of_list [true;true;false;true]) = 2 +*) + +let iter = Array.iter + +let iteri = Array.iteri + +let blit = Array.blit + +let reverse_in_place a = + let len = Array.length a in + if len>0 then ( + for k = 0 to (len-1)/2 do + let t = a.(k) in + a.(k) <- a.(len-1-k); + a.(len-1-k) <- t; + done + ) + +(*$T + reverse_in_place [| |]; true + reverse_in_place [| 1 |]; true + let a = [| 1; 2; 3; 4; 5 |] in \ + reverse_in_place a; \ + a = [| 5;4;3;2;1 |] + let a = [| 1; 2; 3; 4; 5; 6 |] in \ + reverse_in_place a; \ + a = [| 6;5;4;3;2;1 |] +*) + +let sorted cmp a = + let b = Array.copy a in + Array.sort cmp b; + b + +(*$= & ~cmp:(=) ~printer:Q.Print.(array int) + [||] (sorted Pervasives.compare [||]) + [|0;1;2;3;4|] (sorted Pervasives.compare [|3;2;1;4;0|]) +*) + +(*$Q + Q.(array int) (fun a -> \ + let b = Array.copy a in \ + Array.sort Pervasives.compare b; b = sorted Pervasives.compare a) +*) + +let sort_indices cmp a = + let len = Array.length a in + let b = Array.init len (fun k->k) in + Array.sort (fun k1 k2 -> cmp a.(k1) a.(k2)) b; + b + +(*$= & ~cmp:(=) ~printer:Q.Print.(array int) + [||] (sort_indices Pervasives.compare [||]) + [|4;2;1;0;3|] (sort_indices Pervasives.compare [|"d";"c";"b";"e";"a"|]) +*) + +(*$Q + Q.(array printable_string) (fun a -> \ + let b = sort_indices String.compare a in \ + sorted String.compare a = Array.map (Array.get a) b) +*) + +let sort_ranking cmp a = + let cmp_int : int -> int -> int = Pervasives.compare in + sort_indices cmp_int (sort_indices cmp a) + +(*$= & ~cmp:(=) ~printer:Q.Print.(array int) + [||] (sort_ranking Pervasives.compare [||]) + [|3;2;1;4;0|] (sort_ranking Pervasives.compare [|"d";"c";"b";"e";"a"|]) +*) + +(*$Q + Q.(array printable_string) (fun a -> \ + let b = sort_ranking String.compare a in \ + let a_sorted = sorted String.compare a in \ + a = Array.map (Array.get a_sorted) b) +*) + +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 rec find_aux f a i = + if i = Array.length a then None + else match f i a.(i) with + | Some _ as res -> res + | None -> find_aux f a (i+1) + +let find f a = + find_aux (fun _ -> f ) a 0 + +let findi f a = + find_aux f a 0 + +let find_idx p a = + find_aux (fun i x -> if p x then Some (i,x) else None) a 0 + +let filter_map f a = + let rec aux acc i = + if i = Array.length a + then ( + let a' = Array.of_list acc in + reverse_in_place a'; + a' + ) else match f a.(i) with + | None -> aux acc (i+1) + | Some x -> aux (x::acc) (i+1) + in aux [] 0 + +(*$T + filter_map (fun x -> if x mod 2 = 0 then Some (string_of_int x) else None) \ + [| 1; 2; 3; 4 |] = [| "2"; "4" |] + filter_map (fun x -> if x mod 2 = 0 then Some (string_of_int x) else None) \ + [| 1; 2; 3; 4; 5; 6 |] \ + = [| "2"; "4"; "6" |] +*) + +let filter p a = + filter_map (fun x -> if p x then Some x else None) a + +(* append [rev a] in front of [acc] *) +let rec __rev_append_list a acc i = + if i = Array.length a + then acc + else + __rev_append_list a (a.(i) :: acc) (i+1) + +let flat_map f a = + let rec aux acc i = + if i = Array.length a + then ( + let a' = Array.of_list acc in + reverse_in_place a'; + a' + ) + else + let a' = f a.(i) in + aux (__rev_append_list a' acc 0) (i+1) + in aux [] 0 + +(*$T + let a = [| 1; 3; 5 |] in \ + let a' = flat_map (fun x -> [| x; x+1 |]) a in \ + a' = [| 1; 2; 3; 4; 5; 6 |] +*) + +let rec _lookup_rec ~cmp k a i j = + if i>j then raise Not_found + else if i=j + then if cmp k a.(i) = 0 + then i + else raise Not_found + else + let middle = (j+i)/2 in + match cmp k a.(middle) with + | 0 -> middle + | n when n<0 -> _lookup_rec ~cmp k a i (middle-1) + | _ -> _lookup_rec ~cmp k a (middle+1) j + +let _lookup_exn ~cmp k a i j = + if i>j then raise Not_found; + match cmp k a.(i) with + | 0 -> i + | n when n<0 -> raise Not_found (* too low *) + | _ when i=j -> raise Not_found (* too high *) + | _ -> + match cmp k a.(j) with + | 0 -> j + | n when n<0 -> _lookup_rec ~cmp k a (i+1) (j-1) + | _ -> raise Not_found (* too high *) + +let lookup_exn ?(cmp=Pervasives.compare) k a = + _lookup_exn ~cmp k a 0 (Array.length a-1) + +let lookup ?(cmp=Pervasives.compare) k a = + try Some (_lookup_exn ~cmp k a 0 (Array.length a-1)) + with Not_found -> None + +(*$T + lookup 2 [|0;1;2;3;4;5|] = Some 2 + lookup 4 [|0;1;2;3;4;5|] = Some 4 + lookup 0 [|1;2;3;4;5|] = None + lookup 6 [|1;2;3;4;5|] = None + lookup 3 [| |] = None + lookup 1 [| 1 |] = Some 0 + lookup 2 [| 1 |] = None +*) + +let bsearch ?(cmp=Pervasives.compare) k a = + let rec aux i j = + if i > j + then `Just_after j + else + let middle = i + (j - i) / 2 in (* avoid overflow *) + match cmp k a.(middle) with + | 0 -> `At middle + | n when n<0 -> aux i (middle - 1) + | _ -> aux (middle + 1) j + in + let n = Array.length a in + if n=0 then `Empty + else match cmp a.(0) k, cmp a.(n-1) k with + | c, _ when c>0 -> `All_bigger + | _, c when c<0 -> `All_lower + | _ -> aux 0 (n-1) + +(*$T bsearch + bsearch 3 [|1; 2; 2; 3; 4; 10|] = `At 3 + bsearch 5 [|1; 2; 2; 3; 4; 10|] = `Just_after 4 + bsearch 1 [|1; 2; 5; 5; 11; 12|] = `At 0 + bsearch 12 [|1; 2; 5; 5; 11; 12|] = `At 5 + bsearch 10 [|1; 2; 2; 3; 4; 9|] = `All_lower + bsearch 0 [|1; 2; 2; 3; 4; 9|] = `All_bigger + bsearch 3 [| |] = `Empty +*) + +let (>>=) a f = flat_map f a + +let (>>|) a f = map f a + +let (>|=) a f = map f a + +let for_all p a = + let rec aux i = + i = Array.length a || (p a.(i) && aux (i+1)) + in + aux 0 + +let exists p a = + let rec aux i = + i <> Array.length a && (p a.(i) || aux (i+1)) + in + aux 0 + +let rec _for_all2 p a1 a2 i1 i2 ~len = + len=0 || (p a1.(i1) a2.(i2) && _for_all2 p a1 a2 (i1+1) (i2+1) ~len:(len-1)) + +let for_all2 p a b = + Array.length a = Array.length b + && + _for_all2 p a b 0 0 ~len:(Array.length a) + +let rec _exists2 p a1 a2 i1 i2 ~len = + len>0 && (p a1.(i1) a2.(i2) || _exists2 p a1 a2 (i1+1) (i2+1) ~len:(len-1)) + +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 + Array.init (j-i+1) (fun k -> i+k) + else + Array.init (i-j+1) (fun k -> i-k) + +(*$T + (1 -- 4) |> Array.to_list = [1;2;3;4] + (4 -- 1) |> Array.to_list = [4;3;2;1] + (0 -- 0) |> Array.to_list = [0] +*) + +(*$Q + Q.(pair small_int small_int) (fun (a,b) -> \ + (a -- b) |> Array.to_list = CCList.(a -- b)) +*) + +let (--^) i j = + if i=j then [| |] + else if i>j + then Array.init (i-j) (fun k -> i-k) + else Array.init (j-i) (fun k -> i+k) + +(*$Q + Q.(pair small_int small_int) (fun (a,b) -> \ + (a --^ b) |> Array.to_list = CCList.(a --^ b)) +*) + +(** all the elements of a, but the i-th, into a list *) +let except_idx a i = + foldi + (fun acc j elt -> if i = j then acc else elt::acc) + [] a + +let equal eq a b = + let rec aux i = + if i = Array.length a then true + else eq a.(i) b.(i) && aux (i+1) + in + Array.length a = Array.length b + && + aux 0 + +(*$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 = + let rec aux i = + if i = Array.length a + then if i = Array.length b then 0 else -1 + else if i = Array.length b + then 1 + else + let c = cmp a.(i) b.(i) in + if c = 0 then aux (i+1) else c + in + aux 0 + +(*$T + compare CCOrd.compare [| 1; 2; 3 |] [| 1; 2; 3 |] = 0 + compare CCOrd.compare [| 1; 2; 3 |] [| 2; 2; 3 |] < 0 + compare CCOrd.compare [| 1; 2; |] [| 1; 2; 3 |] < 0 + compare CCOrd.compare [| 1; 2; 3 |] [| 1; 2; |] > 0 +*) + +(* shuffle a[i...j[ using the given int random generator + See http://en.wikipedia.org/wiki/Fisher-Yates_shuffle *) +let _shuffle _rand_int a i j = + for k = j-1 downto i+1 do + let l = _rand_int (k+1) in + let tmp = a.(l) in + a.(l) <- a.(k); + a.(k) <- tmp; + done + +let shuffle a = + _shuffle Random.int a 0 (Array.length a) + +let shuffle_with st a = + _shuffle (Random.State.int st) a 0 (Array.length a) + +let rec _to_klist a i j () = + if i=j then `Nil else `Cons (a.(i), _to_klist a (i+1) j) + +let random_choose a st = + let n = Array.length a in + if n = 0 then raise Not_found; + a.(Random.State.int st n) + +let random_len n g st = + Array.init n (fun _ -> g st) + +let random g st = + let n = Random.State.int st 1_000 in + random_len n g st + +let random_non_empty g st = + let n = 1 + Random.State.int st 1_000 in + random_len n g st + +let pp ?(sep=", ") pp_item out a = + for k = 0 to Array.length a-1 do + if k > 0 then (Format.pp_print_string out sep; Format.pp_print_cut out ()); + pp_item out a.(k) + done + +let pp_i ?(sep=", ") pp_item out a = + for k = 0 to Array.length a - 1 do + if k > 0 then (Format.pp_print_string out sep; Format.pp_print_cut out ()); + pp_item k out a.(k) + done + +let to_seq a k = iter k a + +let to_gen a = + let k = ref 0 in + fun () -> + if !k < Array.length a + then ( + let x = a.(!k) in + incr k; + Some x + ) else None + +let to_klist a = _to_klist a 0 (Array.length a) + +(** {2 Generic Functions} *) + +module type MONO_ARRAY = sig + type elt + type t + + val length : t -> int + + val get : t -> int -> elt + + val set : t -> int -> elt -> unit +end + +(* Dual Pivot Quicksort (Yaroslavskiy) + from "average case analysis of Java 7's Dual Pivot Quicksort" *) +module SortGeneric(A : MONO_ARRAY) = struct + module Rand = Random.State + + let seed_ = [|123456|] + + type state = { + mutable l: int; (* left pointer *) + mutable g: int; (* right pointer *) + mutable k: int; + } + + let rand_idx_ rand i j = i + Rand.int rand (j-i) + + let swap_ a i j = + if i=j then () + else ( + let tmp = A.get a i in + A.set a i (A.get a j); + A.set a j tmp + ) + + let sort ~cmp a = + let rec insert_ a i k = + if k 0 then ( + swap_ a k (k+1); + insert_ a i (k-1) + ) + in + (* recursive part of insertion sort *) + let rec sort_insertion_rec a i j k = + if k 1 then sort_insertion_rec a i j (i+1) + in + let rand = Rand.make seed_ in + (* sort slice. + There is a chance that the two pivots are equal, but it's unlikely. *) + let rec sort_slice_ ~st a i j = + if j-i>10 then ( + st.l <- i; + st.g <- j-1; + st.k <- i; + (* choose pivots *) + let p = A.get a (rand_idx_ rand i j) in + let q = A.get a (rand_idx_ rand i j) in + (* invariant: st.p <= st.q, swap them otherwise *) + let p, q = if cmp p q > 0 then q, p else p, q in + while st.k <= st.g do + let cur = A.get a st.k in + if cmp cur p < 0 then ( + (* insert in leftmost band *) + if st.k <> st.l then swap_ a st.k st.l; + st.l <- st.l + 1 + ) else if cmp cur q > 0 then ( + (* insert in rightmost band *) + while st.k < st.g && cmp (A.get a st.g) q > 0 do + st.g <- st.g - 1 + done; + swap_ a st.k st.g; + st.g <- st.g - 1; + (* the element swapped from the right might be in the first situation. + that is, < p (we know it's <= q already) *) + if cmp (A.get a st.k) p < 0 then ( + if st.k <> st.l then swap_ a st.k st.l; + st.l <- st.l + 1 + ) + ); + st.k <- st.k + 1 + done; + (* save values before recursing *) + let l = st.l and g = st.g and sort_middle = cmp p q < 0 in + sort_slice_ ~st a i l; + if sort_middle then sort_slice_ ~st a l (g+1); + sort_slice_ ~st a (g+1) j; + ) else sort_insertion a i j + in + if A.length a > 0 then ( + let st = { l=0; g=A.length a; k=0; } in + sort_slice_ ~st a 0 (A.length a) + ) +end + + +let sort_generic (type arr)(type elt) + (module A : MONO_ARRAY with type t = arr and type elt = elt) + ?(cmp=Pervasives.compare) a + = + let module S = SortGeneric(A) in + S.sort ~cmp a + +(*$inject + module IA = struct + type elt = int + type t = int array + include Array + end + + let gen_arr = Q.Gen.(array_size (1--100) small_int) + let arr_arbitrary = Q.make + ~print:Q.Print.(array int) + ~small:Array.length + ~shrink:Q.Shrink.(array ?shrink:None) + gen_arr +*) + +(*$Q & ~count:300 + arr_arbitrary (fun a -> \ + let a1 = Array.copy a and a2 = Array.copy a in \ + Array.sort CCInt.compare a1; sort_generic ~cmp:CCInt.compare (module IA) a2; \ + a1 = a2 ) +*) diff --git a/src/core/CCArrayLabels.mli b/src/core/CCArrayLabels.mli index 58346689..36f3d4ea 100644 --- a/src/core/CCArrayLabels.mli +++ b/src/core/CCArrayLabels.mli @@ -54,14 +54,14 @@ val reverse_in_place : 'a t -> unit val sorted : f:('a -> 'a -> int) -> 'a t -> 'a array (** [sorted cmp a] makes a copy of [a] and sorts it with [cmp]. - @since NEXT_RELEASE *) + @since 1.0 *) val sort_indices : f:('a -> 'a -> int) -> 'a t -> int array (** [sort_indices cmp a] returns a new array [b], with the same length as [a], such that [b.(i)] is the index of the [i]-th element of [a] in [sort cmp a]. In other words, [map (fun i -> a.(i)) (sort_indices a) = sorted cmp a]. [a] is not modified. - @since NEXT_RELEASE *) + @since 1.0 *) val sort_ranking : f:('a -> 'a -> int) -> 'a t -> int array (** [sort_ranking cmp a] returns a new array [b], with the same length as [a], @@ -73,7 +73,7 @@ val sort_ranking : f:('a -> 'a -> int) -> 'a t -> int array Without duplicates, we also have [lookup_exn a.(i) (sorted a) = (sorted_ranking a).(i)] - @since NEXT_RELEASE *) + @since 1.0 *) val find : f:('a -> 'b option) -> 'a t -> 'b option (** [find f a] returns [Some y] if there is an element [x] such diff --git a/src/core/CCArray_slice.mli b/src/core/CCArray_slice.mli index f0ecffd2..28dd37d4 100644 --- a/src/core/CCArray_slice.mli +++ b/src/core/CCArray_slice.mli @@ -41,7 +41,7 @@ val to_slice : 'a t -> ('a array * int * int) val to_list : 'a t -> 'a list (** Convert directly to a list - @since NEXT_RELEASE *) + @since 1.0 *) val full : 'a array -> 'a t (** Slice that covers the full array *) @@ -82,14 +82,14 @@ val reverse_in_place : 'a t -> unit val sorted : ('a -> 'a -> int) -> 'a t -> 'a array (** [sorted cmp a] makes a copy of [a] and sorts it with [cmp]. - @since NEXT_RELEASE *) + @since 1.0 *) val sort_indices : ('a -> 'a -> int) -> 'a t -> int array (** [sort_indices cmp a] returns a new array [b], with the same length as [a], such that [b.(i)] is the index of the [i]-th element of [a] in [sort cmp a]. In other words, [map (fun i -> a.(i)) (sort_indices a) = sorted cmp a]. [a] is not modified. - @since NEXT_RELEASE *) + @since 1.0 *) val sort_ranking : ('a -> 'a -> int) -> 'a t -> int array (** [sort_ranking cmp a] returns a new array [b], with the same length as [a], @@ -101,7 +101,7 @@ val sort_ranking : ('a -> 'a -> int) -> 'a t -> int array Without duplicates, we also have [lookup_exn a.(i) (sorted a) = (sorted_ranking a).(i)] - @since NEXT_RELEASE *) + @since 1.0 *) val find : ('a -> 'b option) -> 'a t -> 'b option (** [find f a] returns [Some y] if there is an element [x] such diff --git a/src/core/CCChar.mli b/src/core/CCChar.mli index 351e2617..55ebca0d 100644 --- a/src/core/CCChar.mli +++ b/src/core/CCChar.mli @@ -20,15 +20,15 @@ val uppercase_ascii : t -> t val of_int_exn : int -> t (** Alias to {!Char.chr} @raise Invalid_argument if the int is not within [0,...,255] - @since NEXT_RELEASE *) + @since 1.0 *) val of_int : int -> t option (** Safe version of {!of_int} - @since NEXT_RELEASE *) + @since 1.0 *) val to_int : t -> int (** Alias to {!Char.code} - @since NEXT_RELEASE *) + @since 1.0 *) val pp : Buffer.t -> t -> unit val print : Format.formatter -> t -> unit diff --git a/src/core/CCFormat.mli b/src/core/CCFormat.mli index 187577dd..3880dcfd 100644 --- a/src/core/CCFormat.mli +++ b/src/core/CCFormat.mli @@ -84,23 +84,23 @@ val return : ('a, _, _, 'a) format4 -> unit printer - [return "@{and then@}@,"] - [return "@[a@ b@]"] - @since NEXT_RELEASE + @since 1.0 *) val of_to_string : ('a -> string) -> 'a printer (** [of_to_string f] converts its input to a string using [f], then prints the string - @since NEXT_RELEASE *) + @since 1.0 *) val const : 'a printer -> 'a -> unit printer (** [const pp x] is a unit printer that uses [pp] on [x] - @since NEXT_RELEASE *) + @since 1.0 *) val some : 'a printer -> 'a option printer (** [some pp] will print options as follows: - [Some x] is printed using [pp] on [x] - [None] is not printed at all - @since NEXT_RELEASE + @since 1.0 *) (** {2 ANSI codes} @@ -185,7 +185,7 @@ val stderr : t val tee : t -> t -> t (** [tee a b] makes a new formatter that writes in both [a] and [b]. - @since NEXT_RELEASE *) + @since 1.0 *) val sprintf : ('a, t, unit, string) format4 -> 'a (** Print into a string any format string that would usually be compatible diff --git a/src/core/CCHash.mli b/src/core/CCHash.mli index 34e22760..28c5fc54 100644 --- a/src/core/CCHash.mli +++ b/src/core/CCHash.mli @@ -42,12 +42,12 @@ val poly : 'a t val list_comm : 'a t -> 'a list t (** Commutative version of {!list}. Lists that are equal up to permutation will have the same hash. - @since NEXT_RELEASE *) + @since 1.0 *) val array_comm : 'a t -> 'a array t (** Commutative version of {!array}. Arrays that are equal up to permutation will have the same hash. - @since NEXT_RELEASE *) + @since 1.0 *) (** {2 Base hash combinators} *) diff --git a/src/core/CCHashtbl.ml b/src/core/CCHashtbl.ml index 14f64c9c..28634e43 100644 --- a/src/core/CCHashtbl.ml +++ b/src/core/CCHashtbl.ml @@ -230,7 +230,7 @@ module type S = sig in [tbl], if it exists. If it does not exist, then [f k] is called to obtain a new binding [v]; [k -> v] is added to [tbl] and [v] is returned. - @since NEXT_RELEASE *) + @since 1.0 *) val print : key printer -> 'a printer -> 'a t printer (** Printer for tables diff --git a/src/core/CCHashtbl.mli b/src/core/CCHashtbl.mli index 484cca45..127893d8 100644 --- a/src/core/CCHashtbl.mli +++ b/src/core/CCHashtbl.mli @@ -100,7 +100,7 @@ module Poly : sig in [tbl], if it exists. If it does not exist, then [f k] is called to obtain a new binding [v]; [k -> v] is added to [tbl] and [v] is returned. - @since NEXT_RELEASE *) + @since 1.0 *) val print : 'a printer -> 'b printer -> ('a, 'b) Hashtbl.t printer (** Printer for table @@ -199,7 +199,7 @@ module type S = sig in [tbl], if it exists. If it does not exist, then [f k] is called to obtain a new binding [v]; [k -> v] is added to [tbl] and [v] is returned. - @since NEXT_RELEASE *) + @since 1.0 *) val print : key printer -> 'a printer -> 'a t printer (** Printer for tables diff --git a/src/core/CCList.mli b/src/core/CCList.mli index 6edbdd8a..3d5a316b 100644 --- a/src/core/CCList.mli +++ b/src/core/CCList.mli @@ -121,7 +121,7 @@ val sublists_of_len : If [last = CCOpt.return], it will simply keep the last group. By default, [last = fun _ -> None], i.e. the last group is dropped if shorter than [n]. @raise Invalid_argument if [offset <= 0] or [n <= 0] - @since NEXT_RELEASE *) + @since 1.0 *) val pure : 'a -> 'a t diff --git a/src/core/CCListLabels.ml b/src/core/CCListLabels.ml deleted file mode 120000 index 8e52dced..00000000 --- a/src/core/CCListLabels.ml +++ /dev/null @@ -1 +0,0 @@ -CCList.ml \ No newline at end of file diff --git a/src/core/CCListLabels.ml b/src/core/CCListLabels.ml new file mode 100644 index 00000000..d5bb6fad --- /dev/null +++ b/src/core/CCListLabels.ml @@ -0,0 +1,1184 @@ + +(* This file is free software, part of containers. See file "license" for more details. *) + +(** {1 complements to list} *) + +(*$inject + let lsort l = List.sort Pervasives.compare l +*) + +type 'a t = 'a list + +let empty = [] + +let is_empty = function + | [] -> true + | _::_ -> false + +(* max depth for direct recursion *) +let direct_depth_default_ = 1000 + +let map f l = + let rec direct f i l = match l with + | [] -> [] + | [x] -> [f x] + | [x1;x2] -> let y1 = f x1 in [y1; f x2] + | [x1;x2;x3] -> let y1 = f x1 in let y2 = f x2 in [y1; y2; f x3] + | _ when i=0 -> List.rev (List.rev_map f l) + | x1::x2::x3::x4::l' -> + let y1 = f x1 in + let y2 = f x2 in + let y3 = f x3 in + let y4 = f x4 in + y1 :: y2 :: y3 :: y4 :: direct f (i-1) l' + in + direct f direct_depth_default_ l + +(*$Q + (Q.list Q.small_int) (fun l -> \ + let f x = x+1 in \ + List.rev (List.rev_map f l) = map f l) +*) + +let (>|=) l f = map f l + +let direct_depth_append_ = 10_000 + +let cons x l = x::l + +let append l1 l2 = + let rec direct i l1 l2 = match l1 with + | [] -> l2 + | _ when i=0 -> safe l1 l2 + | x::l1' -> x :: direct (i-1) l1' l2 + and safe l1 l2 = + List.rev_append (List.rev l1) l2 + in + match l1 with + | [] -> l2 + | [x] -> x::l2 + | [x;y] -> x::y::l2 + | _ -> direct direct_depth_append_ l1 l2 + +let (@) = append + +(*$T + [1;2;3] @ [4;5;6] = [1;2;3;4;5;6] + (1-- 10_000) @ (10_001 -- 20_000) = 1 -- 20_000 +*) + +let cons_maybe o l = match o with + | Some x -> x :: l + | None -> l + +(*$T + cons_maybe (Some 1) [2;3] = [1;2;3] + cons_maybe None [2;3] = [2;3] +*) + +let direct_depth_filter_ = 10_000 + +let filter p l = + let rec direct i p l = match l with + | [] -> [] + | _ when i=0 -> safe p l [] + | x::l' when not (p x) -> direct i p l' + | x::l' -> x :: direct (i-1) p l' + and safe p l acc = match l with + | [] -> List.rev acc + | x::l' when not (p x) -> safe p l' acc + | x::l' -> safe p l' (x::acc) + in + direct direct_depth_filter_ p l + +(*$= & ~printer:CCInt.to_string + 500 (filter (fun x->x mod 2 = 0) (1 -- 1000) |> List.length) + 50_000 (filter (fun x->x mod 2 = 0) (1 -- 100_000) |> List.length) + 500_000 (filter (fun x->x mod 2 = 0) (1 -- 1_000_000) |> List.length) +*) + +let fold_right f l acc = + let rec direct i f l acc = match l with + | [] -> acc + | _ when i=0 -> safe f (List.rev l) acc + | x::l' -> + let acc = direct (i-1) f l' acc in + f x acc + and safe f l acc = match l with + | [] -> acc + | x::l' -> + let acc = f x acc in + safe f l' acc + in + direct direct_depth_default_ f l acc + +(*$T + fold_right (+) (1 -- 1_000_000) 0 = \ + List.fold_left (+) 0 (1 -- 1_000_000) +*) + +(*$Q + (Q.list Q.small_int) (fun l -> \ + l = fold_right (fun x y->x::y) l []) +*) + +let rec fold_while f acc = function + | [] -> acc + | e::l -> let acc, cont = f acc e in + match cont with + | `Stop -> acc + | `Continue -> fold_while f acc l + +(*$T + fold_while (fun acc b -> if b then acc+1, `Continue else acc, `Stop) 0 [true;true;false;true] = 2 +*) + +let fold_map f acc l = + let rec aux f acc map_acc l = match l with + | [] -> acc, List.rev map_acc + | x :: l' -> + let acc, y = f acc x in + aux f acc (y :: map_acc) l' + in + aux f acc [] l + +(*$= + (6, ["1"; "2"; "3"]) \ + (fold_map (fun acc x->acc+x, string_of_int x) 0 [1;2;3]) +*) + +(*$Q + Q.(list int) (fun l -> \ + fold_map (fun acc x -> x::acc, x) [] l = (List.rev l, l)) +*) + +let fold_map2 f acc l1 l2 = + let rec aux f acc map_acc l1 l2 = match l1, l2 with + | [], [] -> acc, List.rev map_acc + | [], _ + | _, [] -> invalid_arg "fold_map2" + | x1 :: l1', x2 :: l2' -> + let acc, y = f acc x1 x2 in + aux f acc (y :: map_acc) l1' l2' + in + aux f acc [] l1 l2 + +(*$= + (310, ["1 10"; "2 0"; "3 100"]) \ + (fold_map2 (fun acc x y->acc+x*y, string_of_int x ^ " " ^ string_of_int y) \ + 0 [1;2;3] [10;0;100]) +*) + +(*$T + (try ignore (fold_map2 (fun _ _ _ -> assert false) 42 [] [1]); false \ + with Invalid_argument _ -> true) +*) + +let fold_filter_map f acc l = + let rec aux f acc map_acc l = match l with + | [] -> acc, List.rev map_acc + | x :: l' -> + let acc, y = f acc x in + aux f acc (cons_maybe y map_acc) l' + in + aux f acc [] l + +(*$= & ~printer:Q.Print.(pair int (list int)) + (List.fold_left (+) 0 (1--10), [2;4;6;8;10]) \ + (fold_filter_map (fun acc x -> acc+x, if x mod 2 = 0 then Some x else None) \ + 0 (1--10)) +*) + +let fold_flat_map f acc l = + let rec aux f acc map_acc l = match l with + | [] -> acc, List.rev map_acc + | x :: l' -> + let acc, y = f acc x in + aux f acc (List.rev_append y map_acc) l' + in + aux f acc [] l + +(*$= + (6, ["1"; "a1"; "2"; "a2"; "3"; "a3"]) \ + (let pf = Printf.sprintf in \ + fold_flat_map (fun acc x->acc+x, [pf "%d" x; pf "a%d" x]) 0 [1;2;3]) +*) + +(*$Q + Q.(list int) (fun l -> \ + fold_flat_map (fun acc x -> x::acc, [x;x+10]) [] l = \ + (List.rev l, flat_map (fun x->[x;x+10]) l) ) +*) + +let init len f = + let rec init_rec acc i f = + if i=0 then f i :: acc + else init_rec (f i :: acc) (i-1) f + in + if len<0 then invalid_arg "init" + else if len=0 then [] + else init_rec [] (len-1) f + +(*$T + init 0 (fun _ -> 0) = [] + init 1 (fun x->x) = [0] + init 1000 (fun x->x) = 0--999 +*) + +let rec compare f l1 l2 = match l1, l2 with + | [], [] -> 0 + | _, [] -> 1 + | [], _ -> -1 + | x1::l1', x2::l2' -> + let c = f x1 x2 in + if c <> 0 then c else compare f l1' l2' + +let rec equal f l1 l2 = match l1, l2 with + | [], [] -> true + | [], _ | _, [] -> false + | x1::l1', x2::l2' -> f x1 x2 && equal f l1' l2' + +(*$T + equal CCInt.equal (1--1_000_000) (1--1_000_000) +*) + +let flat_map f l = + let rec aux f l kont = match l with + | [] -> kont [] + | x::l' -> + let y = f x in + let kont' tail = match y with + | [] -> kont tail + | [x] -> kont (x :: tail) + | [x;y] -> kont (x::y::tail) + | l -> kont (append l tail) + in + aux f l' kont' + in + aux f l (fun l->l) + +(*$T + flat_map (fun x -> [x+1; x*2]) [10;100] = [11;20;101;200] + List.length (flat_map (fun x->[x]) (1--300_000)) = 300_000 +*) + +let flatten l = fold_right append l [] + +(*$T + flatten [[1]; [2;3;4]; []; []; [5;6]] = 1--6 + flatten (init 300_001 (fun x->[x])) = 0--300_000 +*) + +let product f l1 l2 = + flat_map (fun x -> map (fun y -> f x y) l2) l1 + +let fold_product f acc l1 l2 = + List.fold_left + (fun acc x1 -> + List.fold_left + (fun acc x2 -> f acc x1 x2) + acc l2 + ) acc l1 + +let diagonal l = + let rec gen acc l = match l with + | [] -> acc + | x::l' -> + let acc = List.fold_left (fun acc y -> (x,y) :: acc) acc l' in + gen acc l' + in + gen [] l + +(*$T + diagonal [] = [] + diagonal [1] = [] + diagonal [1;2] = [1,2] + diagonal [1;2;3] |> List.sort Pervasives.compare = [1, 2; 1, 3; 2, 3] +*) + +let partition_map f l = + let rec iter f l1 l2 l = match l with + | [] -> List.rev l1, List.rev l2 + | x :: tl -> + match f x with + | `Left y -> iter f (y :: l1) l2 tl + | `Right y -> iter f l1 (y :: l2) tl + | `Drop -> iter f l1 l2 tl + in + iter f [] [] l + +(*$R + let l1, l2 = + partition_map (function + | n when n = 0 -> `Drop + | n when n mod 2 = 0 -> `Left n + | n -> `Right n + ) [0;1;2;3;4] + in + assert_equal [2;4] l1; + assert_equal [1;3] l2 +*) + +let return x = [x] + +let (>>=) l f = flat_map f l + +let (<$>) = map + +let pure = return + +let (<*>) funs l = product (fun f x -> f x) funs l + +let sorted_merge ?(cmp=Pervasives.compare) l1 l2 = + let rec recurse cmp acc l1 l2 = match l1,l2 with + | [], _ -> List.rev_append acc l2 + | _, [] -> List.rev_append acc l1 + | x1::l1', x2::l2' -> + let c = cmp x1 x2 in + if c < 0 then recurse cmp (x1::acc) l1' l2 + else if c > 0 then recurse cmp (x2::acc) l1 l2' + else recurse cmp (x1::x2::acc) l1' l2' + in + recurse cmp [] l1 l2 + +(*$T + List.sort Pervasives.compare ([(( * )2); ((+)1)] <*> [10;100]) \ + = [11; 20; 101; 200] + sorted_merge [1;1;2] [1;2;3] = [1;1;1;2;2;3] +*) + +(*$Q + Q.(pair (list int) (list int)) (fun (l1,l2) -> \ + List.length (sorted_merge l1 l2) = List.length l1 + List.length l2) +*) + +let sort_uniq (type elt) ?(cmp=Pervasives.compare) l = + let module S = Set.Make(struct + type t = elt + let compare = cmp + end) in + let set = fold_right S.add l S.empty in + S.elements set + +(*$T + sort_uniq [1;2;5;3;6;1;4;2;3] = [1;2;3;4;5;6] + sort_uniq [] = [] + sort_uniq [10;10;10;10;1;10] = [1;10] +*) + +let is_sorted ?(cmp=Pervasives.compare) l = + let rec aux cmp = function + | [] | [_] -> true + | x :: ((y :: _) as tail) -> cmp x y <= 0 && aux cmp tail + in + aux cmp l + +(*$Q + Q.(list small_int) (fun l -> \ + is_sorted (List.sort Pervasives.compare l)) +*) + +let sorted_insert ?(cmp=Pervasives.compare) ?(uniq=false) x l = + let rec aux cmp uniq x left l = match l with + | [] -> List.rev_append left [x] + | y :: tail -> + match cmp x y with + | 0 -> + let l' = if uniq then l else x :: l in + List.rev_append left l' + | n when n<0 -> List.rev_append left (x :: l) + | _ -> aux cmp uniq x (y::left) tail + in + aux cmp uniq x [] l + +(*$Q + Q.(pair small_int (list small_int)) (fun (x,l) -> \ + let l = List.sort Pervasives.compare l in \ + is_sorted (sorted_insert ~uniq:true x l)) + Q.(pair small_int (list small_int)) (fun (x,l) -> \ + let l = List.sort Pervasives.compare l in \ + is_sorted (sorted_insert ~uniq:false x l)) + Q.(pair small_int (list small_int)) (fun (x,l) -> \ + let l = List.sort Pervasives.compare l in \ + let l' = sorted_insert ~uniq:false x l in \ + List.length l' = List.length l + 1) + Q.(pair small_int (list small_int)) (fun (x,l) -> \ + let l = List.sort Pervasives.compare l in \ + List.mem x (sorted_insert x l)) +*) + +let uniq_succ ?(eq=(=)) l = + let rec f acc l = match l with + | [] -> List.rev acc + | [x] -> List.rev (x::acc) + | x :: ((y :: _) as tail) when eq x y -> f acc tail + | x :: tail -> f (x::acc) tail + in + f [] l + +(*$T + uniq_succ [1;1;2;3;1;6;6;4;6;1] = [1;2;3;1;6;4;6;1] +*) + +let group_succ ?(eq=(=)) l = + let rec f ~eq acc cur l = match cur, l with + | [], [] -> List.rev acc + | _::_, [] -> List.rev (List.rev cur :: acc) + | [], x::tl -> f ~eq acc [x] tl + | (y :: _), x :: tl when eq x y -> f ~eq acc (x::cur) tl + | _, x :: tl -> f ~eq (List.rev cur :: acc) [x] tl + in + f ~eq [] [] l + +(*$T + group_succ [1;2;3;1;1;2;4] = [[1]; [2]; [3]; [1;1]; [2]; [4]] + group_succ [] = [] + group_succ [1;1;1] = [[1;1;1]] + group_succ [1;2;2;2] = [[1]; [2;2;2]] + group_succ ~eq:(fun (x,_)(y,_)-> x=y) [1, 1; 1, 2; 1, 3; 2, 0] \ + = [[1, 1; 1, 2; 1, 3]; [2, 0]] +*) + +let sorted_merge_uniq ?(cmp=Pervasives.compare) l1 l2 = + let push ~cmp acc x = match acc with + | [] -> [x] + | y :: _ when cmp x y > 0 -> x :: acc + | _ -> acc (* duplicate, do not yield *) + in + let rec recurse ~cmp acc l1 l2 = match l1,l2 with + | [], l + | l, [] -> + let acc = List.fold_left (push ~cmp) acc l in + List.rev acc + | x1::l1', x2::l2' -> + let c = cmp x1 x2 in + if c < 0 then recurse ~cmp (push ~cmp acc x1) l1' l2 + else if c > 0 then recurse ~cmp (push ~cmp acc x2) l1 l2' + else recurse ~cmp acc l1 l2' (* drop one of the [x] *) + in + recurse ~cmp [] l1 l2 + +(*$T + sorted_merge_uniq [1; 1; 2; 3; 5; 8] [1; 2; 3; 4; 6; 8; 9; 9] = [1;2;3;4;5;6;8;9] +*) + +(*$Q + Q.(list int) (fun l -> \ + let l = List.sort Pervasives.compare l in \ + sorted_merge_uniq l [] = uniq_succ l) + Q.(list int) (fun l -> \ + let l = List.sort Pervasives.compare l in \ + sorted_merge_uniq [] l = uniq_succ l) + Q.(pair (list int) (list int)) (fun (l1, l2) -> \ + let l1 = List.sort Pervasives.compare l1 \ + and l2 = List.sort Pervasives.compare l2 in \ + let l3 = sorted_merge_uniq l1 l2 in \ + uniq_succ l3 = l3) +*) + +let take n l = + let rec direct i n l = match l with + | [] -> [] + | _ when i=0 -> safe n [] l + | x::l' -> + if n > 0 + then x :: direct (i-1) (n-1) l' + else [] + and safe n acc l = match l with + | [] -> List.rev acc + | _ when n=0 -> List.rev acc + | x::l' -> safe (n-1) (x::acc) l' + in + direct direct_depth_default_ n l + +(*$T + take 2 [1;2;3;4;5] = [1;2] + take 10_000 (range 0 100_000) |> List.length = 10_000 + take 10_000 (range 0 2_000) = range 0 2_000 + take 300_000 (1 -- 400_000) = 1 -- 300_000 +*) + +(*$Q + (Q.pair (Q.list Q.small_int) Q.int) (fun (l,i) -> \ + let i = abs i in \ + let l1 = take i l in \ + List.length l1 <= i && ((List.length l1 = i) = (List.length l >= i))) +*) + +let rec drop n l = match l with + | [] -> [] + | _ when n=0 -> l + | _::l' -> drop (n-1) l' + +let hd_tl = function + | [] -> failwith "hd_tl" + | x :: l -> x, l + +(*$T + try ignore (hd_tl []); false with Failure _ -> true + hd_tl [1;2;3] = (1, [2;3]) +*) + +let take_drop n l = take n l, drop n l + +(*$Q + (Q.pair (Q.list Q.small_int) Q.int) (fun (l,i) -> \ + let i = abs i in \ + let l1, l2 = take_drop i l in \ + l1 @ l2 = l ) +*) + +let sublists_of_len ?(last=fun _ -> None) ?offset n l = + if n < 1 then invalid_arg "sublists_of_len: n must be > 0"; + let offset = match offset with + | None -> n + | Some o when o < 1 -> invalid_arg "sublists_of_len: offset must be > 0" + | Some o -> o + in + (* add sub-lists of [l] to [acc] *) + let rec aux acc l = + let group = take n l in + if group=[] then acc (* this was the last group, we are done *) + else if List.length group < n (* last group, with missing elements *) + then match last group with + | None -> acc + | Some group' -> group' :: acc + else ( + let l' = drop offset l in + aux (group :: acc) l' (* continue *) + ) + in + List.rev (aux [] l) + +(*$= sublists_of_len as subs & ~printer:Q.Print.(list (list int)) + [[1;2;3]] (subs 3 [1;2;3;4]) + [[1;2]; [3;4]; [5;6]] (subs 2 [1;2;3;4;5;6]) + [] (subs 3 [1;2]) + [[1;2];[3;4]] (subs 2 ~offset:2 [1;2;3;4]) + [[1;2];[2;3]] (subs 2 ~offset:1 [1;2;3]) + [[1;2];[4;5]] (subs 2 ~offset:3 [1;2;3;4;5;6]) + [[1;2;3];[4]] (subs 3 ~last:CCOpt.return [1;2;3;4]) + [[1;2]; [3;4]] (subs 2 [1;2;3;4;5]) +*) + +let take_while p l = + let rec direct i p l = match l with + | [] -> [] + | _ when i=0 -> safe p [] l + | x :: l' -> + if p x then x :: direct (i-1) p l' else [] + and safe p acc l = match l with + | [] -> List.rev acc + | x :: l' -> + if p x then safe p (x::acc) l' else List.rev acc + in + direct direct_depth_default_ p l + +(*$T + take_while (fun x->x<10) (1 -- 20) = (1--9) + take_while (fun x->x <> 0) [0;1;2;3] = [] + take_while (fun _ -> true) [] = [] + take_while (fun _ -> true) (1--10) = (1--10) +*) + +(*$Q + Q.(pair (fun1 small_int bool) (list small_int)) (fun (f,l) -> \ + let l1 = take_while f l in \ + List.for_all f l1) +*) + +let rec drop_while p l = match l with + | [] -> [] + | x :: l' -> if p x then drop_while p l' else l + +(*$Q + Q.(pair (fun1 small_int bool) (list small_int)) (fun (f,l) -> \ + take_while f l @ drop_while f l = l) +*) + +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 + | _ :: tl -> find_pred p tl + +let find_pred_exn p l = match find_pred p l with + | None -> raise Not_found + | Some x -> x + +(*$T + find_pred ((=) 4) [1;2;5;4;3;0] = Some 4 + find_pred (fun _ -> true) [] = None + find_pred (fun _ -> false) (1 -- 10) = None + find_pred (fun x -> x < 10) (1 -- 9) = Some 1 +*) + +let find_mapi f l = + let rec aux f i = function + | [] -> None + | x::l' -> + match f i x with + | Some _ as res -> res + | None -> aux f (i+1) l' + in aux f 0 l + +let find_map f l = find_mapi (fun _ -> f) l + +let find_idx p l = find_mapi (fun i x -> if p x then Some (i, x) else None) l + +(*$T + find_map (fun x -> if x=3 then Some "a" else None) [1;2;3;4] = Some "a" + find_map (fun x -> if x=3 then Some "a" else None) [1;2;4;5] = None +*) + +let remove ?(eq=(=)) ~x l = + let rec remove' eq x acc l = match l with + | [] -> List.rev acc + | y :: tail when eq x y -> remove' eq x acc tail + | y :: tail -> remove' eq x (y::acc) tail + in + remove' eq x [] l + +(*$T + remove ~x:1 [2;1;3;3;2;1] = [2;3;3;2] + remove ~x:10 [1;2;3] = [1;2;3] +*) + +let filter_map f l = + let rec recurse acc l = match l with + | [] -> List.rev acc + | x::l' -> + let acc' = match f x with | None -> acc | Some y -> y::acc in + recurse acc' l' + in recurse [] l + +(*$= + ["2"; "4"] \ + (filter_map (fun x -> if x mod 2 = 0 then Some (string_of_int x) else None) \ + [1;2;3;4;5]) + [ "2"; "4"; "6" ] \ + (filter_map (fun x -> if x mod 2 = 0 then Some (string_of_int x) else None) \ + [ 1; 2; 3; 4; 5; 6 ]) +*) + +let mem ?(eq=(=)) x l = + let rec search eq x l = match l with + | [] -> false + | y::l' -> eq x y || search eq x l' + in search eq x l + +let add_nodup ?(eq=(=)) x l = + if mem ~eq x l then l else x::l + +let remove_one ?(eq=(=)) x l = + let rec remove_one ~eq x acc l = match l with + | [] -> assert false + | y :: tl when eq x y -> List.rev_append acc tl + | y :: tl -> remove_one ~eq x (y::acc) tl + in + if mem ~eq x l then remove_one ~eq x [] l else l + +(*$Q + Q.(pair int (list int)) (fun (x,l) -> \ + remove_one x (add_nodup x l) = l) + Q.(pair int (list int)) (fun (x,l) -> \ + mem x l || List.length (add_nodup x l) = List.length l + 1) + Q.(pair int (list int)) (fun (x,l) -> \ + not (mem x l) || List.length (remove_one x l) = List.length l - 1) +*) + +let subset ?(eq=(=)) l1 l2 = + List.for_all + (fun t -> mem ~eq t l2) + l1 + +let uniq ?(eq=(=)) l = + let rec uniq eq acc l = match l with + | [] -> List.rev acc + | x::xs when List.exists (eq x) xs -> uniq eq acc xs + | x::xs -> uniq eq (x::acc) xs + in uniq eq [] l + +(*$T + uniq [1;1;2;2;3;4;4;2;4;1;5] |> List.sort Pervasives.compare = [1;2;3;4;5] +*) + +let union ?(eq=(=)) l1 l2 = + let rec union eq acc l1 l2 = match l1 with + | [] -> List.rev_append acc l2 + | x::xs when mem ~eq x l2 -> union eq acc xs l2 + | x::xs -> union eq (x::acc) xs l2 + in union eq [] l1 l2 + +(*$T + union [1;2;4] [2;3;4;5] = [1;2;3;4;5] +*) + +let inter ?(eq=(=)) l1 l2 = + let rec inter eq acc l1 l2 = match l1 with + | [] -> List.rev acc + | x::xs when mem ~eq x l2 -> inter eq (x::acc) xs l2 + | _::xs -> inter eq acc xs l2 + in inter eq [] l1 l2 + +(*$T + inter [1;2;4] [2;3;4;5] = [2;4] +*) + +let mapi f l = + let r = ref 0 in + map + (fun x -> + let y = f !r x in + incr r; y + ) l + +(*$T + mapi (fun i x -> i*x) [10;10;10] = [0;10;20] +*) + +let iteri f l = + let rec aux f i l = match l with + | [] -> () + | x::l' -> f i x; aux f (i+1) l' + in aux f 0 l + +let foldi f acc l = + let rec foldi f acc i l = match l with + | [] -> acc + | x::l' -> + let acc = f acc i x in + foldi f acc (i+1) l' + in + foldi f acc 0 l + +let rec get_at_idx_exn i l = match l with + | [] -> raise Not_found + | x::_ when i=0 -> x + | _::l' -> get_at_idx_exn (i-1) l' + +let get_at_idx i l = + try Some (get_at_idx_exn i l) + with Not_found -> None + +(*$T + get_at_idx 0 (range 0 10) = Some 0 + get_at_idx 5 (range 0 10) = Some 5 + get_at_idx 11 (range 0 10) = None + get_at_idx 0 [] = None +*) + +let set_at_idx i x l0 = + let rec aux l acc i = match l with + | [] -> l0 + | _::l' when i=0 -> List.rev_append acc (x::l') + | y::l' -> + aux l' (y::acc) (i-1) + in + aux l0 [] i + +(*$T + set_at_idx 0 10 [1;2;3] = [10;2;3] + set_at_idx 4 10 [1;2;3] = [1;2;3] + set_at_idx 1 10 [1;2;3] = [1;10;3] +*) + +let insert_at_idx i x l = + let rec aux l acc i x = match l with + | [] -> List.rev_append acc [x] + | y::l' when i=0 -> List.rev_append acc (x::y::l') + | y::l' -> + aux l' (y::acc) (i-1) x + in + aux l [] i x + +(*$T + insert_at_idx 0 10 [1;2;3] = [10;1;2;3] + insert_at_idx 4 10 [1;2;3] = [1;2;3;10] + insert_at_idx 1 10 [1;2;3] = [1;10;2;3] +*) + +let remove_at_idx i l0 = + let rec aux l acc i = match l with + | [] -> l0 + | _::l' when i=0 -> List.rev_append acc l' + | y::l' -> + aux l' (y::acc) (i-1) + in + aux l0 [] i + +(*$T + remove_at_idx 0 [1;2;3;4] = [2;3;4] + remove_at_idx 3 [1;2;3;4] = [1;2;3] + remove_at_idx 5 [1;2;3;4] = [1;2;3;4] +*) + +let range_by ~step i j = + let rec range i j acc = + if i=j then i::acc else range i (j-step) (j::acc) + in + if step = 0 then + raise (Invalid_argument "CCList.range_by") + else if (if step > 0 then i>j else i \ + let i = min i j and j = max i j in \ + range_by ~step:1 i j = range i j) +*) + +let range i j = + let rec up i j acc = + if i=j then i::acc else up i (j-1) (j::acc) + and down i j acc = + if i=j then i::acc else down i (j+1) (j::acc) + in + if i<=j then up i j [] else down i j [] + +(*$T + range 0 5 = [0;1;2;3;4;5] + range 0 0 = [0] + range 5 2 = [5;4;3;2] +*) + +let range' i j = + if i \ + let l = (a--^b) in not (List.mem b l)) +*) + +let replicate i x = + let rec aux acc i = + if i = 0 then acc + else aux (x::acc) (i-1) + in aux [] i + +let repeat i l = + let l' = List.rev l in + let rec aux acc i = + if i = 0 then List.rev acc + else aux (List.rev_append l' acc) (i-1) + in aux [] i + +module Assoc = struct + type ('a, 'b) t = ('a*'b) list + + let rec search_exn eq l x = match l with + | [] -> raise Not_found + | (y,z)::l' -> + if eq x y then z else search_exn eq l' x + + let get_exn ?(eq=(=)) x l = search_exn eq l x + + let get ?(eq=(=)) x l = + try Some (search_exn eq l x) + with Not_found -> None + + (*$T + Assoc.get 1 [1, "1"; 2, "2"] = Some "1" + Assoc.get 2 [1, "1"; 2, "2"] = Some "2" + Assoc.get 3 [1, "1"; 2, "2"] = None + Assoc.get 42 [] = None + *) + + (* search for a binding for [x] in [l], and calls [f x (Some v) rest] + or [f x None rest] depending on whether it finds the binding. + [rest] is the list of the other bindings *) + let rec search_set eq acc l x ~f = match l with + | [] -> f x None acc + | (x',y')::l' -> + if eq x x' + then f x (Some y') (List.rev_append acc l') + else search_set eq ((x',y')::acc) l' x ~f + + let set ?(eq=(=)) x y l = + search_set eq [] l x + ~f:(fun x _ l -> (x,y)::l) + + (*$T + Assoc.set 2 "two" [1,"1"; 2, "2"] |> List.sort Pervasives.compare \ + = [1, "1"; 2, "two"] + Assoc.set 3 "3" [1,"1"; 2, "2"] |> List.sort Pervasives.compare \ + = [1, "1"; 2, "2"; 3, "3"] + *) + + let mem ?(eq=(=)) x l = + try ignore (search_exn eq l x); true + with Not_found -> false + + (*$T + Assoc.mem 1 [1,"1"; 2,"2"; 3, "3"] + not (Assoc.mem 4 [1,"1"; 2,"2"; 3, "3"]) + *) + + let update ?(eq=(=)) ~f x l = + search_set eq [] l x + ~f:(fun x opt_y rest -> + match f opt_y with + | None -> rest (* drop *) + | Some y' -> (x,y') :: rest) + (*$= + [1,"1"; 2,"22"] \ + (Assoc.update 2 [1,"1"; 2,"2"] \ + ~f:(function Some "2" -> Some "22" | _ -> assert false) |> lsort) + [1,"1"; 3,"3"] \ + (Assoc.update 2 [1,"1"; 2,"2"; 3,"3"] \ + ~f:(function Some "2" -> None | _ -> assert false) |> lsort) + [1,"1"; 2,"2"; 3,"3"] \ + (Assoc.update 3 [1,"1"; 2,"2"] \ + ~f:(function None -> Some "3" | _ -> assert false) |> lsort) + *) + + let remove ?(eq=(=)) x l = + search_set eq [] l x + ~f:(fun _ opt_y rest -> match opt_y with + | None -> l (* keep as is *) + | Some _ -> rest) + + (*$= + [1,"1"] \ + (Assoc.remove 2 [1,"1"; 2,"2"] |> lsort) + [1,"1"; 3,"3"] \ + (Assoc.remove 2 [1,"1"; 2,"2"; 3,"3"] |> lsort) + [1,"1"; 2,"2"] \ + (Assoc.remove 3 [1,"1"; 2,"2"] |> lsort) + *) +end + +(** {2 References on Lists} *) + +module Ref = struct + type 'a t = 'a list ref + + let push l x = l := x :: !l + + let pop l = match !l with + | [] -> None + | x::tail -> + l := tail; + Some x + + let pop_exn l = match !l with + | [] -> failwith "CCList.Ref.pop_exn" + | x::tail -> + l := tail; + x + + let create() = ref [] + + let clear l = l := [] + + let lift f l = f !l + + let push_list r l = + r := List.rev_append l !r + + (*$T + let l = Ref.create() in Ref.push l 1; Ref.push_list l [2;3]; !l = [3;2;1] + *) +end + +(** {2 Monadic Operations} *) +module type MONAD = sig + type 'a t + val return : 'a -> 'a t + val (>>=) : 'a t -> ('a -> 'b t) -> 'b t +end + +module Traverse(M : MONAD) = struct + open M + + let map_m f l = + let rec aux f acc l = match l with + | [] -> return (List.rev acc) + | x::tail -> + f x >>= fun x' -> + aux f (x' :: acc) tail + in aux f [] l + + let rec map_m_par f l = match l with + | [] -> M.return [] + | x::tl -> + let x' = f x in + let tl' = map_m_par f tl in + x' >>= fun x' -> + tl' >>= fun tl' -> + M.return (x'::tl') + + let sequence_m l = map_m (fun x->x) l + + let rec fold_m f acc l = match l with + | [] -> return acc + | x :: l' -> + f acc x + >>= fun acc' -> + fold_m f acc' l' +end + +(** {2 Conversions} *) + +type 'a sequence = ('a -> unit) -> unit +type 'a gen = unit -> 'a option +type 'a klist = unit -> [`Nil | `Cons of 'a * 'a klist] +type 'a printer = Format.formatter -> 'a -> unit +type 'a random_gen = Random.State.t -> 'a + +let random_len len g st = + init len (fun _ -> g st) + +(*$T + random_len 10 CCInt.random_small (Random.State.make [||]) |> List.length = 10 +*) + +let random g st = + let len = Random.State.int st 1_000 in + random_len len g st + +let random_non_empty g st = + let len = 1 + Random.State.int st 1_000 in + random_len len g st + +let random_choose l = match l with + | [] -> raise Not_found + | _::_ -> + let len = List.length l in + fun st -> + let i = Random.State.int st len in + List.nth l i + +let random_sequence l st = map (fun g -> g st) l + +let to_seq l k = List.iter k l +let of_seq seq = + let l = ref [] in + seq (fun x -> l := x :: !l); + List.rev !l + +let to_gen l = + let l = ref l in + fun () -> + match !l with + | [] -> None + | x::l' -> + l := l'; Some x + +let of_gen g = + let rec direct i g = + if i = 0 then safe [] g + else match g () with + | None -> [] + | Some x -> x :: direct (i-1) g + and safe acc g = match g () with + | None -> List.rev acc + | Some x -> safe (x::acc) g + in + direct direct_depth_default_ g + +let to_klist l = + let rec make l () = match l with + | [] -> `Nil + | x::l' -> `Cons (x, make l') + in make l + +let of_klist l = + let rec direct i g = + if i = 0 then safe [] g + else match l () with + | `Nil -> [] + | `Cons (x,l') -> x :: direct (i-1) l' + and safe acc l = match l () with + | `Nil -> List.rev acc + | `Cons (x,l') -> safe (x::acc) l' + in + direct direct_depth_default_ l + +module Infix = struct + let (>|=) = (>|=) + let (@) = (@) + let (<*>) = (<*>) + let (<$>) = (<$>) + let (>>=) = (>>=) + let (--) = (--) + let (--^) = (--^) +end + +(** {2 IO} *) + +let pp ?(start="") ?(stop="") ?(sep=", ") pp_item fmt l = + let rec print fmt l = match l with + | x::((_::_) as l) -> + pp_item fmt x; + Format.pp_print_string fmt sep; + Format.pp_print_cut fmt (); + print fmt l + | x::[] -> pp_item fmt x + | [] -> () + in + Format.pp_print_string fmt start; + print fmt l; + Format.pp_print_string fmt stop + +(*$= & ~printer:(fun s->s) + "[1, 2, 3]" \ + (CCFormat.to_string \ + (CCFormat.hbox(CCList.pp ~start:"[" ~stop:"]" CCFormat.int)) \ + [1;2;3]) +*) diff --git a/src/core/CCResult.mli b/src/core/CCResult.mli index 3f0d73a3..f236274c 100644 --- a/src/core/CCResult.mli +++ b/src/core/CCResult.mli @@ -90,12 +90,12 @@ val fold : ok:('a -> 'b) -> error:('err -> 'b) -> ('a, 'err) t -> 'b val is_ok : ('a, 'err) t -> bool (** Return true if Ok/ - @since NEXT_RELEASE *) + @since 1.0 *) val is_error : ('a, 'err) t -> bool (** Return true if Error - @since NEXT_RELEASE *) + @since 1.0 *) (** {2 Wrappers} *)