Задача : Создайте генератор типа Gen[(Set[T],Set[U])]
так, чтобы для каждой сгенерированной пары наборов каждый набор в паре имел одинаковый размер.
Следующая функция
import org.scalacheck.Gen
def genSameSizeSets[T,U](gt: Gen[T], gu: Gen[U]): Gen[(Set[T],Set[U])] = {
for { n <- Gen.posNum[Long] // or .oneOf(1 to MAX_SET_SIZE)
tset <- Gen.containerOfN[Set,T](n, gt)
uset <- Gen.containerOfN[Set,U](n, gu)
minsize = Math.min(tset.size, uset.size)
} yield (tset.take(minsize), uset.take(minsize))
}
создает желаемый генератор.
Ключевым моментом этого генератора является то, что он полностью избегает сброса кандидатов.
containerOfN
сам по себе не может гарантировать размер результирующего Set
, поскольку для этого потребуется gt
и gu
для генерации n
последовательных различных значений.
Альтернативная реализация заключалась бы в том, чтобы поместить в охранное предложение if
для понимания
if tset.size == uset.size
Это была первая попытка. Это был не надежный генератор, потому что он имел высокий коэффициент сброса и ScalaCheck
сдался перед прохождением.
В этом случае есть простой выход. Вместо того, чтобы отбрасывать несоответствующих кандидатов, просто приведите большее к тому же размеру, что и меньшее (которое все еще не пусто). Поскольку заданные значения являются произвольными, не имеет значения, какие из них отбрасываются. Эта логика реализована с Math.min
и take
.
Похоже, это важный принцип правильной конструкции генератора: " избегайте сбросов, таких как чума ".
Вот полный рабочий пример:
import org.scalacheck.Properties
import org.scalacheck.Gen
import org.scalacheck.Arbitrary
import org.scalacheck.Prop.{forAll,collect}
object StackOverflowExample extends Properties("same size sets") {
def genSameSizeSets[T,U](gt: Gen[T], gu: Gen[U]): Gen[(Set[T],Set[U])] = {
for { n <- Gen.posNum[Int]
ts <- Gen.containerOfN[Set,T](n, gt)
us <- Gen.containerOfN[Set,U](n, gu)
if us.size == ts.size
minsize = Math.min(ts.size, us.size)
} yield (ts.take(minsize), us.take(minsize))
}
val g = genSameSizeSets(Arbitrary.arbitrary[Int], Arbitrary.arbitrary[Char])
property("same size") = forAll(g) { case (intSet, charSet) =>
collect(intSet.size, charSet.size) { intSet.size == charSet.size }
}
}
с этим выводом
+ same size sets.same size: OK, passed 100 tests.
> Collected test data:
8% (11,11)
7% (2,2)
7% (17,17)
6% (16,16)
<snip>
1% (44,44)
1% (27,27)
1% (26,26)
1% (56,56)