我正在尝试为匹配算法开发基于属性的测试,我需要生成两个相同大小的输入集以提供给算法。我目前对解决方案的尝试如下:
case class Man(id: Long, quality: Long, ordering: Ordering[Woman])
case class Woman(id: Long, quality: Long, ordering: Ordering[Man])
val man: Gen[Man] = {
for {
id <- Gen.posNum[Long]
quality <- Gen.posNum[Long]
} yield Man(id, quality, Man.womanByQuality)
}
val woman: Gen[Woman] = {
for {
id <- Gen.posNum[Long]
quality <- Gen.posNum[Long]
} yield Woman(id, quality, Woman.manByQuality)
}
def setOfN[T](n: Int, g: Gen[T]): Gen[Set[T]] = {
Gen.containerOfN[Set, T](n, g)
}
def unMatched: Gen[(Set[Man], Set[Woman])] = Gen.sized {
n => setOfN(n, man).flatMap(ms => setOfN(ms.size, woman).map(ws => (ms, ws)))
}
这会根据需要生成输入集的元组,但不保证它们的大小相同。当我使用...
运行测试时property("all men and women are matched") = forAll(unMatched) {
case (ms, ws) =>
println((ms.size, ws.size))
val matches = DeferredAcceptance.weaklyStableMatching(ms, ws)
(matches.size == ms.size) && (matches.size == ws.size)
}
控制台将打印类似......
的内容(0,0)
(1,1)
(2,2)
(3,2)
(1,2)
(0,2)
(0,1)
(0,0)
! marriage-market.all men and women are matched: Exception raised on proper
ty evaluation.
> ARG_0: (Set(),Set(Woman(1,1,scala.math.Ordering$$anon$10@3d8314f0)))
> ARG_0_ORIGINAL: (Set(Man(3,1,scala.math.Ordering$$anon$10@2bea5ab4), Man(
2,1,scala.math.Ordering$$anon$10@2bea5ab4), Man(2,3,scala.math.Ordering$$
anon$10@2bea5ab4)),Set(Woman(1,1,scala.math.Ordering$$anon$10@3d8314f0),
Woman(3,2,scala.math.Ordering$$anon$10@3d8314f0)))
> Exception: java.lang.IllegalArgumentException: requirement failed
scala.Predef$.require(Predef.scala:264)
org.economicsl.matching.DeferredAcceptance$.weaklyStableMatching(DeferredAc
ceptance.scala:97)
org.economicsl.matching.MarriageMarketSpecification$.$anonfun$new$2(Marriag
eMarketSpecification.scala:54)
org.economicsl.matching.MarriageMarketSpecification$.$anonfun$new$2$adapted
(MarriageMarketSpecification.scala:51)
org.scalacheck.Prop$.$anonfun$forAllShrink$2(Prop.scala:761)
Found 1 failing properties.
Process finished with exit code 1
测试失败,因为我已经要求两个输入集必须具有相同的大小。我的意图是生成器应该提供有效的输入数据。
思想?
答案 0 :(得分:1)
问题:构造类型为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)
答案 1 :(得分:0)
我偶然发现了以下解决方案。
def unMatched: Gen[(Set[Man], Set[Woman])] = Gen.sized {
n => setOfN(n, man).flatMap(ms => setOfN(ms.size, woman).map(ws => (ms, ws))).suchThat { case (ms, ws) => ms.size == ws.size }
}
但我认为不应该使用suchThat
组合子。问题似乎是size
参数被视为容器大小的上限(而不是等式约束)。
根据@FlorianK的评论更新
我发现问题出在我对Man
和Woman
生成器的规范上。这些生成器不是生成器不同的值。而不是使用肯定的Long
来表示我使用Java id
切换到的唯一UUID
。正确的发电机是
val man: Gen[Man] = {
for {
id <- Gen.uuid
quality <- Gen.posNum[Long]
} yield Man(id, quality, Man.womanByQuality)
}
val woman: Gen[Woman] = {
for {
id <- Gen.uuid
quality <- Gen.posNum[Long]
} yield Woman(id, quality, Woman.manByQuality)
}
我不太确定为什么原始发电机没有按预期工作。他们当然有可能产生非独特的实例,但我认为它应该非常罕见(猜我错了!)。
答案 2 :(得分:0)
我设法使用以下代码生成大小相等的List[Int]
对:
val pairOfListGen = Gen.sized { size => for {
x <- Gen.containerOfN[List, Int](size, Gen.choose(0,50000))
y <- Gen.containerOfN[List, Int](size, Gen.choose(0,50000))
} yield (x,y)
}
Man.womanByQuality
未在您的代码示例中定义,因此我无法使用您的生成器对其进行测试,但我希望这对您有用。