Skip to content

Commit 4bc5052

Browse files
committed
fix Gen.{nat,pos}_split{2,}
fixes #180
1 parent b065a81 commit 4bc5052

4 files changed

Lines changed: 98 additions & 36 deletions

File tree

src/core/QCheck.ml

Lines changed: 33 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -248,27 +248,27 @@ module Gen = struct
248248
List.sort (fun (w1, _) (w2, _) -> poly_compare w1 w2) samples |> List.rev_map snd
249249

250250
let range_subset ~size low high st =
251-
if not (low <= high && size <= high - low + 1) then invalid_arg "Gen.range_subset";
251+
let range_size = high - low + 1 in
252+
if not (0 <= size && size <= range_size) then
253+
invalid_arg "Gen.range_subset";
252254
(* The algorithm below is attributed to Floyd, see for example
253255
https://eyalsch.wordpress.com/2010/04/01/random-sample/
254256
https://math.stackexchange.com/questions/178690
255257
256-
Note: the code be made faster by checking membership in [arr]
257-
directly instead of using an additional Set. None of our
258-
dependencies implements dichotomic search, so using Set is
259-
easier.
258+
Note: the code is easier to read when drawing from [0..range_size-1]
259+
rather than [low..high]. We draw in [0..bound], and shift the
260+
results by adding [low] when writing them to the result array.
260261
*)
261262
let module ISet = Set.Make(Int) in
262263
let s = ref ISet.empty in
263-
let arr = Array.make size 0 in
264-
for i = high - size to high do
265-
let pos = int_range high i st in
266-
let choice =
267-
if ISet.mem pos !s then i else pos
268-
in
269-
arr.(i - low) <- choice;
264+
for i = range_size - size to range_size - 1 do
265+
let pos = int_range 0 i st in
266+
let choice = if ISet.mem pos !s then i else pos in
270267
s := ISet.add choice !s;
271268
done;
269+
let arr = Array.make size 0 in
270+
let idx = ref 0 in
271+
ISet.iter (fun choice -> arr.(!idx) <- low + choice; incr idx) !s;
272272
arr
273273

274274
let array_subset size arr st =
@@ -334,31 +334,36 @@ module Gen = struct
334334

335335
(* nat splitting *)
336336

337-
let nat_split2 n st =
338-
if (n < 2) then invalid_arg "nat_split2";
337+
let pos_split2 n st =
338+
if (n < 2) then invalid_arg "pos_split2";
339339
let n1 = int_range 1 (n - 1) st in
340340
(n1, n - n1)
341341

342-
let pos_split2 n st =
342+
let nat_split2 n st =
343343
let n1 = int_range 0 n st in
344344
(n1, n - n1)
345345

346346
let pos_split ~size:k n st =
347-
if (k > n) then invalid_arg "nat_split";
348-
(* To split n into n{0}+n{1}+..+n{k-1}, we draw distinct "boundaries"
349-
b{-1}..b{k-1}, with b{-1}=0 and b{k-1} = n
350-
and the k-1 intermediate boundaries b{0}..b{k-2}
351-
chosen randomly distinct in [1;n-1].
352-
353-
Then each n{i} is defined as b{i}-b{i-1}. *)
354-
let b = range_subset ~size:(k-1) 1 (n - 1) st in
355-
Array.init k (fun i ->
356-
if i = 0 then b.(0)
357-
else if i = k-1 then n - b.(i-1)
358-
else b.(i) - b.(i-1)
359-
)
347+
if 0 = k && 0 = n then [||]
348+
else begin
349+
if not (0 < k && k <= n) then invalid_arg "pos_split";
350+
(* To split n into n{0}+n{1}+..+n{k-1}, we draw distinct "boundaries"
351+
b{-1}..b{k-1}, with b{-1}=0 and b{k-1} = n
352+
and the k-1 intermediate boundaries b{0}..b{k-2}
353+
chosen randomly distinct in [1;n-1].
354+
355+
Then each n{i} is defined as b{i}-b{i-1}. *)
356+
let b = range_subset ~size:(k-1) 1 (n - 1) st in
357+
if k = 1 then [|n|]
358+
else Array.init k (fun i ->
359+
if i = 0 then b.(0)
360+
else if i = k-1 then n - b.(i-1)
361+
else b.(i) - b.(i-1)
362+
)
363+
end
360364

361365
let nat_split ~size:k n st =
366+
if not (0 <= k && 0 <= n) then invalid_arg "nat_split";
362367
pos_split ~size:k (n+k) st
363368
|> Array.map (fun v -> v - 1)
364369

src/core/QCheck.mli

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -467,22 +467,22 @@ module Gen : sig
467467
468468
This is useful to split sizes to combine sized generators.
469469
470-
@raise Invalid_argument unless [n >= 2].
471-
472470
@since 0.18
473471
*)
474472

475473
val pos_split2 : int -> (int * int) t
476-
(** [nat_split2 n] generates pairs [(n1, n2)] of strictly positive
474+
(** [pos_split2 n] generates pairs [(n1, n2)] of strictly positive
477475
(nonzero) natural numbers with [n1 + n2 = n].
478476
477+
@raise Invalid_argument unless [n >= 2].
478+
479479
This is useful to split sizes to combine sized generators.
480480
481481
@since 0.18
482482
*)
483483

484484
val nat_split : size:int -> int -> int array t
485-
(** [nat_split2 ~size:k n] generates [k]-sized arrays [n1,n2,..nk]
485+
(** [nat_split ~size:k n] generates [k]-sized arrays [n1,n2,..nk]
486486
of natural numbers in [[0;n]] with [n1 + n2 + ... + nk = n].
487487
488488
This is useful to split sizes to combine sized generators.
@@ -493,15 +493,15 @@ module Gen : sig
493493
*)
494494

495495
val pos_split : size:int -> int -> int array t
496-
(** [nat_split2 ~size:k n] generates [k]-sized arrays [n1,n2,..nk]
496+
(** [pos_split ~size:k n] generates [k]-sized arrays [n1,n2,..nk]
497497
of strictly positive (non-zero) natural numbers with
498498
[n1 + n2 + ... + nk = n].
499499
500500
This is useful to split sizes to combine sized generators.
501501
502502
Complexity O(k log k).
503503
504-
@raise Invalid_argument unless [k <= n].
504+
@raise Invalid_argument unless [0 < k <= n] or [0 = k = n].
505505
506506
@since 0.18
507507
*)

test/core/QCheck_expect_test.ml

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,16 @@ module Overall = struct
8383
true)
8484
end
8585

86-
(* positive tests of the various generators *)
86+
(* positive tests of the various generators
87+
88+
Note: it is important to disable shrinking for these tests, as the
89+
shrinkers will suggest inputs that are coming from the generator
90+
themselves -- which we want to test -- so their reduced
91+
counter-example are confusing rather than helpful.
92+
93+
This is achieved by using (Test.make ~print ...), without a ~shrink
94+
argument.
95+
*)
8796
module Generator = struct
8897
open QCheck
8998

@@ -127,6 +136,50 @@ module Generator = struct
127136
~name:"tree_rev_is_involutive"
128137
QCheck.(make IntTree.gen_tree)
129138
(fun tree -> IntTree.(rev_tree (rev_tree tree)) = tree)
139+
140+
let nat_split2_spec =
141+
Test.make ~name:"nat_split2 spec"
142+
(make
143+
~print:Print.(pair int (pair int int))
144+
Gen.(small_nat >>= fun n ->
145+
pair (return n) (nat_split2 n)))
146+
(fun (n, (a, b)) ->
147+
0 <= a && 0 <= b && a + b = n)
148+
149+
let pos_split2_spec =
150+
Test.make ~name:"pos_split2 spec"
151+
(make
152+
~print:Print.(pair int (pair int int))
153+
Gen.(small_nat >>= fun n ->
154+
(* we need n > 2 *)
155+
let n = n + 2 in
156+
pair (return n) (pos_split2 n)))
157+
(fun (n, (a, b)) ->
158+
(0 < a && 0 < b && a + b = n))
159+
160+
let nat_split =
161+
Test.make ~name:"nat_split"
162+
(make
163+
~print:Print.(pair int (array int))
164+
Gen.(small_nat >>= fun n ->
165+
pair (return n) (nat_split ~size:n n)))
166+
(fun (n, arr) ->
167+
Array.length arr = n
168+
&& Array.for_all (fun k -> 0 <= k) arr
169+
&& Array.fold_left (+) 0 arr = n)
170+
171+
let pos_split =
172+
Test.make ~name:"pos_split"
173+
(make
174+
~print:Print.(triple int int (array int))
175+
Gen.(pair small_nat small_nat >>= fun (m, n) ->
176+
(* we need both size>0 and n>0 and size <= n *)
177+
let size = 1 + min m n and n = 1 + max m n in
178+
triple (return size) (return n) (pos_split ~size n)))
179+
(fun (m, n, arr) ->
180+
Array.length arr = m
181+
&& Array.for_all (fun k -> 0 < k) arr
182+
&& Array.fold_left (+) 0 arr = n)
130183
end
131184

132185
(* negative tests that exercise shrinking behaviour *)
@@ -407,6 +460,10 @@ let _ =
407460
Generator.list_repeat_test;
408461
Generator.array_repeat_test;
409462
Generator.passing_tree_rev;
463+
Generator.nat_split2_spec;
464+
Generator.pos_split2_spec;
465+
Generator.nat_split;
466+
Generator.pos_split;
410467
(*Shrink.test_fac_issue59;*)
411468
Shrink.big_bound_issue59;
412469
Shrink.long_shrink;

test/core/qcheck_output.txt.expected

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -863,7 +863,7 @@ stats dist:
863863
4150517416584649600.. 4611686018427387903: ################# 189
864864
================================================================================
865865
1 warning(s)
866-
failure (26 tests failed, 1 tests errored, ran 66 tests)
866+
failure (26 tests failed, 1 tests errored, ran 70 tests)
867867
random seed: 153870556
868868

869869
+++ Stats for int_dist_empty_bucket ++++++++++++++++++++++++++++++++++++++++++++++++++++++++

0 commit comments

Comments
 (0)