Skip to content

Commit

Permalink
Improve doc for Gen.pick
Browse files Browse the repository at this point in the history
Gen.pick, Gen.someOf, and Gen.atLeastOne will give a randomly chosen
subset of elements, but not in a random order.  This is because
Gen.pick is implemented with reservoir sampling but without a shuffle
operation.  Adding shuffling might be possible, but it could impact
performance and would violate the "do one thing well" principle of
Gen.pick.

For now, just document the situation that results aren't permuted in
the User Guide and in the API docs.

Use a simpler example, picking only 3 of 4, for the distribution
property check of Gen.pick in the test suite.
  • Loading branch information
ashawley committed Aug 10, 2018
1 parent c461962 commit e9dcb28
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 4 deletions.
33 changes: 33 additions & 0 deletions doc/UserGuide.md
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,39 @@ examples.
There is also `Gen.nonEmptyContainerOf` for generating non-empty containers, and
`Gen.containerOfN` for generating containers of a given size.

To generate a container by picking an arbitrary number of elements use
`Gen.someOf`, or by picking one or more elements with
`Gen.atLeastOne`.

```scala
val zeroOrMoreDigits = Gen.someOf(1 to 9)

val oneOrMoreDigits = Gen.atLeastOne(1 to 9)
```

Here are generators that randomly pick `n` elements from a container
with `Gen.pick`:

```scala
val fiveDice: Gen[Seq[Int]] = Gen.pick(5, 1 to 6)

val threeLetters: Gen[Seq[Char]] = Gen.pick(3, 'A' to 'Z')
```

Note that `Gen.someOf`, `Gen.atLeastOne`, and `Gen.pick` only randomly
select elements. They do not generate permutations of the result
with elements in different orders.

To make your generator artificially permute the order of elements, you
can run `scala.util.Random.shuffle` on each of the generated containers
with the `map` method.

```scala
import scala.util.Random

val threeLettersPermuted = threeLetters.map(Random.shuffle(_))
```

**The `arbitrary` Generator**

There is a special generator, `org.scalacheck.Arbitrary.arbitrary`, which
Expand Down
11 changes: 9 additions & 2 deletions jvm/src/test/scala/org/scalacheck/GenSpecification.scala
Original file line number Diff line number Diff line change
Expand Up @@ -198,9 +198,16 @@ object GenSpecification extends Properties("Gen") {
}
}

/**
* Expect:
* 25% 1, 2, 3
* 25% 1, 2, 4
* 25% 1, 4, 3
* 25% 4, 2, 3
*/
property("distributed pick") = {
val lst = (0 to 7).toIterable
val n = 2
val lst = (1 to 4).toIterable
val n = 3
forAll(pick(n, lst)) { xs: Seq[Int] =>
xs.map { x: Int =>
Prop.collect(x) {
Expand Down
10 changes: 8 additions & 2 deletions src/main/scala/org/scalacheck/Gen.scala
Original file line number Diff line number Diff line change
Expand Up @@ -686,7 +686,10 @@ object Gen extends GenArities{
def atLeastOne[T](g1: Gen[T], g2: Gen[T], gs: Gen[T]*) =
choose(1, gs.length+2).flatMap(pick(_, g1, g2, gs: _*))

/** A generator that picks a given number of elements from a list, randomly */
/** A generator that randomly picks a given number of elements from a list
*
* The elements are not guaranteed to be permuted in random order.
*/
def pick[T](n: Int, l: Iterable[T]): Gen[Seq[T]] = {
if (n > l.size || n < 0) throw new IllegalArgumentException(s"invalid choice: $n")
else if (n == 0) Gen.const(Nil)
Expand All @@ -711,7 +714,10 @@ object Gen extends GenArities{
}
}

/** A generator that picks a given number of elements from a list, randomly */
/** A generator that randomly picks a given number of elements from a list
*
* The elements are not guaranteed to be permuted in random order.
*/
def pick[T](n: Int, g1: Gen[T], g2: Gen[T], gn: Gen[T]*): Gen[Seq[T]] = {
val gs = g1 +: g2 +: gn
pick(n, 0 until gs.size).flatMap(idxs =>
Expand Down

0 comments on commit e9dcb28

Please sign in to comment.