了解 ScalaChecks' 'generation size'
Understanding ScalaChecks' 'generation size'
ScalaCheck 的 Gen
API docs 解释 lazy val sized
:
def sized[T](f: (Int) ⇒ Gen[T]): Gen[T]
Creates a generator that can access its generation size
看下面的例子:
import org.scalacheck.Gen.{sized, posNum}
scala> sized( s => { println(s); posNum[Int] })
res12: org.scalacheck.Gen[Int] = org.scalacheck.Gen$$anon@6a071cc0
scala> res12.sample
100
res13: Option[Int] = Some(12)
scala> res12.sample
100
res14: Option[Int] = Some(40)
generation size
是什么意思,即上面输出的100?
生成大小是生成器将生成的结果数。 sized
方法只是让您编写知道自己大小的生成器,这样您就可以将该信息用作生成内容的一个因素。
例如,此生成器(来自 resource)生成两个数字列表,其中 1/3 为正数,2/3 为负数:
import org.scalacheck.Gen
import org.scalacheck.Prop.forAll
val myGen = Gen.sized { size =>
val positiveNumbers = size / 3
val negativeNumbers = size * 2 / 3
for {
posNumList <- Gen.listOfN(positiveNumbers, Gen.posNum[Int])
negNumList <- Gen.listOfN(negativeNumbers, Gen.posNum[Int] map (n => -n))
} yield (size, posNumList, negNumList)
}
check {
forAll(myGen) {
case (genSize, posN, negN) =>
posN.length == genSize / 3 && negN.length == genSize * 2 / 3
}
}
有点像 Scala 集合中的 zipWithIndex
,sized
只是为您提供元信息来帮助您做您需要做的事情。
sized
提供对 Scalacheck 的 "size" 参数的访问。此参数表示 "big" 生成器生成的值应该如何。此参数在几种情况下很有用:
- 您想限制生成的值的数量以生成 属性 并因此更快地测试 运行。
- 您需要将生成的数据纳入外部约束,例如检查字符串长度的表单验证器,或对列施加限制的数据库。
- 您需要生成递归数据结构并在某个点终止。
Gen.sized
的同伴是 Gen.resize
,它允许您更改生成器的大小,如 Gen.resize(10, Gen.alphaNumString)
中那样,它将生成不超过 10 个字符的字母数字字符串.
大多数内置生成器以某种方式使用 sized
,例如 Gen.buildableOf
(这是所有列表和容器生成器的基础):
[…] The size of the container is bounded by the size parameter used when generating values.
一个简单的例子
要了解如何使用 Gen.size
,请查看 "Sized Generators"(Generators,Scalacheck 用户指南)中的示例:
def matrix[T](g: Gen[T]): Gen[Seq[Seq[T]]] = Gen.sized { size =>
val side = scala.math.sqrt(size).asInstanceOf[Int]
Gen.listOfN(side, Gen.listOfN(side, g))
}
此生成器使用 "size" 来限制矩阵的维度,以便整个矩阵的条目永远不会超过 "size" 参数。换句话说,如您的问题大小为 100,生成的矩阵将有 10 行和 10 列,总共有 100 个条目。
递归数据结构
"size" 对于确保递归数据结构的生成器终止特别有用。考虑以下生成二叉树实例并使用 size
限制每个分支的高度以确保生成器在某个点终止的示例:
import org.scalacheck.Gen
import org.scalacheck.Arbitrary.arbitrary
sealed abstract class Tree
case class Node(left: Tree, right: Tree, v: Int) extends Tree
case object Leaf extends Tree
val genLeaf = Gen.const(Leaf)
val genNode = for {
v <- arbitrary[Int]
left <- Gen.sized(h => Gen.resize(h/2, genTree))
right <- Gen.sized(h => Gen.resize(h/2, genTree))
} yield Node(left, right, v)
def genTree: Gen[Tree] = Gen.sized { height =>
if (height <= 0) {
genLeaf
} else {
Gen.oneOf(genLeaf, genNode)
}
}
请注意节点生成器如何递归生成树,但只允许生成 "size" 的一半。反过来,树生成器只会在其大小用尽时生成叶子。因此生成器的"size"是生成树高度的上限,确保生成器在某个点终止并且不会生成过大的树。
请注意,在此示例中,大小仅为树的高度设置了 上限 。它不影响生成树的平衡或生成具有一定深度的树的可能性。这些仅取决于 genTree
中定义的偏差。
在 oneOf
的情况下,每个子树有 50% 的几率只是一个叶子,在这个分支处结束树的生长,这使得生成一个完整的树,稍微耗尽了 "whole" 大小不太可能。
frequency
(见下文)让您编码不同的偏差。在下面的示例中,节点比叶子更有可能,因此下面的生成器生成的树更有可能生长,但它仍然不太可能完整。
与Gen.frequency
的关系
Gen.frequency
用于不同的用例:您不会使用它来限制数据结构的深度或大小,而是为生成器的选择添加一定的偏差。看一下Gen.option
的定义:
def option[T](g: Gen[T]): Gen[Option[T]] =
frequency(1 -> const(None), 9 -> some(g))
此定义使用 frequency
使 None
的不太有趣的情况比 Some
更有趣的情况更不可能。
事实上,我们可以在上面的二叉树示例中组合 Gen.sized
和 Gen.frequency
,使 genTree
更有可能生成 "interesting" 个节点,而不是 [=88] =] 叶子:
def genTree: Gen[Tree] = Gen.sized { height =>
if (height <= 0) {
genLeaf
} else {
Gen.frequency(1 -> genLeaf, 9 -> genNode)
}
}
ScalaCheck 的 Gen
API docs 解释 lazy val sized
:
def sized[T](f: (Int) ⇒ Gen[T]): Gen[T]
Creates a generator that can access its generation size
看下面的例子:
import org.scalacheck.Gen.{sized, posNum}
scala> sized( s => { println(s); posNum[Int] })
res12: org.scalacheck.Gen[Int] = org.scalacheck.Gen$$anon@6a071cc0
scala> res12.sample
100
res13: Option[Int] = Some(12)
scala> res12.sample
100
res14: Option[Int] = Some(40)
generation size
是什么意思,即上面输出的100?
生成大小是生成器将生成的结果数。 sized
方法只是让您编写知道自己大小的生成器,这样您就可以将该信息用作生成内容的一个因素。
例如,此生成器(来自 resource)生成两个数字列表,其中 1/3 为正数,2/3 为负数:
import org.scalacheck.Gen
import org.scalacheck.Prop.forAll
val myGen = Gen.sized { size =>
val positiveNumbers = size / 3
val negativeNumbers = size * 2 / 3
for {
posNumList <- Gen.listOfN(positiveNumbers, Gen.posNum[Int])
negNumList <- Gen.listOfN(negativeNumbers, Gen.posNum[Int] map (n => -n))
} yield (size, posNumList, negNumList)
}
check {
forAll(myGen) {
case (genSize, posN, negN) =>
posN.length == genSize / 3 && negN.length == genSize * 2 / 3
}
}
有点像 Scala 集合中的 zipWithIndex
,sized
只是为您提供元信息来帮助您做您需要做的事情。
sized
提供对 Scalacheck 的 "size" 参数的访问。此参数表示 "big" 生成器生成的值应该如何。此参数在几种情况下很有用:
- 您想限制生成的值的数量以生成 属性 并因此更快地测试 运行。
- 您需要将生成的数据纳入外部约束,例如检查字符串长度的表单验证器,或对列施加限制的数据库。
- 您需要生成递归数据结构并在某个点终止。
Gen.sized
的同伴是 Gen.resize
,它允许您更改生成器的大小,如 Gen.resize(10, Gen.alphaNumString)
中那样,它将生成不超过 10 个字符的字母数字字符串.
大多数内置生成器以某种方式使用 sized
,例如 Gen.buildableOf
(这是所有列表和容器生成器的基础):
[…] The size of the container is bounded by the size parameter used when generating values.
一个简单的例子
要了解如何使用 Gen.size
,请查看 "Sized Generators"(Generators,Scalacheck 用户指南)中的示例:
def matrix[T](g: Gen[T]): Gen[Seq[Seq[T]]] = Gen.sized { size =>
val side = scala.math.sqrt(size).asInstanceOf[Int]
Gen.listOfN(side, Gen.listOfN(side, g))
}
此生成器使用 "size" 来限制矩阵的维度,以便整个矩阵的条目永远不会超过 "size" 参数。换句话说,如您的问题大小为 100,生成的矩阵将有 10 行和 10 列,总共有 100 个条目。
递归数据结构
"size" 对于确保递归数据结构的生成器终止特别有用。考虑以下生成二叉树实例并使用 size
限制每个分支的高度以确保生成器在某个点终止的示例:
import org.scalacheck.Gen
import org.scalacheck.Arbitrary.arbitrary
sealed abstract class Tree
case class Node(left: Tree, right: Tree, v: Int) extends Tree
case object Leaf extends Tree
val genLeaf = Gen.const(Leaf)
val genNode = for {
v <- arbitrary[Int]
left <- Gen.sized(h => Gen.resize(h/2, genTree))
right <- Gen.sized(h => Gen.resize(h/2, genTree))
} yield Node(left, right, v)
def genTree: Gen[Tree] = Gen.sized { height =>
if (height <= 0) {
genLeaf
} else {
Gen.oneOf(genLeaf, genNode)
}
}
请注意节点生成器如何递归生成树,但只允许生成 "size" 的一半。反过来,树生成器只会在其大小用尽时生成叶子。因此生成器的"size"是生成树高度的上限,确保生成器在某个点终止并且不会生成过大的树。
请注意,在此示例中,大小仅为树的高度设置了 上限 。它不影响生成树的平衡或生成具有一定深度的树的可能性。这些仅取决于 genTree
中定义的偏差。
在 oneOf
的情况下,每个子树有 50% 的几率只是一个叶子,在这个分支处结束树的生长,这使得生成一个完整的树,稍微耗尽了 "whole" 大小不太可能。
frequency
(见下文)让您编码不同的偏差。在下面的示例中,节点比叶子更有可能,因此下面的生成器生成的树更有可能生长,但它仍然不太可能完整。
与Gen.frequency
的关系Gen.frequency
用于不同的用例:您不会使用它来限制数据结构的深度或大小,而是为生成器的选择添加一定的偏差。看一下Gen.option
的定义:
def option[T](g: Gen[T]): Gen[Option[T]] =
frequency(1 -> const(None), 9 -> some(g))
此定义使用 frequency
使 None
的不太有趣的情况比 Some
更有趣的情况更不可能。
事实上,我们可以在上面的二叉树示例中组合 Gen.sized
和 Gen.frequency
,使 genTree
更有可能生成 "interesting" 个节点,而不是 [=88] =] 叶子:
def genTree: Gen[Tree] = Gen.sized { height =>
if (height <= 0) {
genLeaf
} else {
Gen.frequency(1 -> genLeaf, 9 -> genNode)
}
}