为任意长度相同的整数列表制定一个任意

时间:2019-05-28 14:49:46

标签: f# generator fscheck

我需要一些帮助来进行有关f#中生成器的练习。

功能

List.zip : ('a list -> 'b list -> ('a * 'b) list)

List.unzip : (('a * 'b) list -> 'a list * 'b list)

在对列表进行运算的条件下彼此相反 相同的长度。 为一对长度相同的整数列表制定一个任意数组

我试图写一些代码:

let length xs ys = 
    List.length xs = List.length ys

let samelength = 
    Arb.filter length Arb.from<int list>

它不起作用,我在相同长度的长度中遇到类型不匹配:

  

错误:类型不匹配。期望为'a list -> bool但给定为'a list -> 'b list -> bool。 bool类型与'a list -> bool类型不匹配。

编辑:

根据建议,我尝试遵循步骤概述,但被困住了。

let sizegen =
    Arb.filter (fun x -> x > 0) Arb.from<int>

let listgen =
    let size = sizegen 
    let xs = Gen.listOfLength size
    let ys = Gen.listOfLength size
    xs, ys

当然还有错误类型不匹配:

  

错误:键入mistmatch。预期为int类型,但此处为Arbitrary<int>

类型

修改

我解决了该练习,但是当我进行测试时,我的生成器似乎无法正常工作,看来又调用了另一个生成器。

let samelength (xs, ys) = 
   List.length xs = List.length ys

let arbMyGen2 = Arb.filter samelength Arb.from<int list * int list> 

type MyGeneratorZ =
   static member arbMyGen2() = 
    {
        new Arbitrary<int list * int list>() with
            override x.Generator = arbMyGen2 |> Arb.toGen
            override x.Shrinker t = Seq.empty
    }

let _ = Arb.register<MyGeneratorZ>()

let pro_zip (xs: int list, ys: int list) = 
   (xs, ys) = List.unzip(List.zip xs ys)

do Check.Quick pro_zip

我得到了错误:

  

错误:System.ArgumentException:list1比list2短1个元素

但是为什么呢?我的生成器只应生成两个长度相同的列表。

1 个答案:

答案 0 :(得分:4)

如果我们查看API reference for the Arb module,并将鼠标悬停在filter的定义上,您会看到Arb.filter的类型是:

pred:('a -> bool) -> a:Arbitrary<'a> -> a:Arbitrary<'a>

这意味着谓词应该是一个返回bool的参数的函数。但是您的length函数是两个参数的函数。您希望将其转换为仅具有一个参数的函数。

这样想。当您编写Arb.filter length Arb.from<int list>时,您的意思是“我想生成一个任意的int list(一次只生成一个),并根据length规则对其进行过滤。”但是您编写的length规则接受两个列表并比较它们的长度。如果FsCheck仅生成一个整数列表,它将与它的长度进行比较吗?没有第二个列表可以比较,因此编译器实际上无法将您的代码转换为有意义的代码。

您可能想做的(尽管有一个问题,我将在稍后讨论)是生成一个 pair 列表,然后将其传递给您的{{1}谓词。也就是说,您可能想要length。这将生成一对彼此完全独立的整数列表。然后,您在Arb.from<int list * int list>函数中仍然会遇到类型不匹配的情况,但是您只需要将其签名从length转换为let length xs ys =,例如让它接收一个包含一对列表的单个参数,而不是将每个列表作为单独的参数。经过这些调整后,您的代码如下所示:

let length (xs,ys) =

但是这仍然存在问题。具体来说,如果我们查看FsCheck documentation,就会发现以下警告:

  

使用let length (xs,ys) = List.length xs = List.length ys let samelength = Arb.filter length Arb.from<int list * int list> 时,请确保为谓词提供很高的返回Gen.filter的机会。如果谓词丢弃“太多”的候选项,则可能导致测试运行速度变慢或根本无法终止。

顺便说一下,这与true一样适用于Arb.filter。您的代码当前的状态,这是一个问题,因为您的过滤器将丢弃大多数列表对。由于列表是彼此独立生成的,因此大多数情况下它们的长度是不同的,因此您的过滤器通常会返回Gen.filter。我建议使用另一种方法。既然您已经说过这是一个练习,所以我不会为您编写代码,因为您可以自己动手学习更多;我只给您概述您要采取的步骤。

  1. 生成一个非负整数false,该整数将为该对中两个列表的大小。 (要获得奖励积分,请使用n来获取您应生成的数据的“当前大小”,并将Gen.sized生成为介于0和n之间的值,以便您的列表对生成器,例如FsCheck的默认列表生成器,将创建从小开始并逐渐变大的列表。
  2. 使用size生成两个列表。 (您甚至可以执行Gen.listOfLength n来轻松生成一对大小相同的列表)。
  3. 别忘了为一对列表编写适当的收缩器,因为练习希望您生成适当的Gen.two (Gen.listOfLength n),而没有收缩器的Arbitrary并不是非常好在实践中很有用。您可能可以在此处使用Arbitrary进行某些操作,其中映射器为Arb.mapFilter,因为您已经在生成匹配长度的列表,但是过滤器是您的id谓词。然后使用length将生成器和收缩器功能转换为正确的Arb.fromGenShrink实例。

如果该轮廓不足以使您正常工作,请问另一个问题,无论您身在何处,我都会很乐意为您提供帮助。

编辑:

在尝试使用Arbitrary编写列表生成器的编辑中,您有以下无效的代码:

sizegen

这里let listgen = let size = sizegen let xs = Gen.listOfLength size let ys = Gen.listOfLength size xs, ys sizegen,而您想从中提取Gen<int>参数。有多种方法可以执行此操作,但最简单的方法是FsCheck为我们提供的int计算表达式。

顺便说一句,如果您不知道什么是计算表达式,它们就是F#最强大的功能:它们在幕后非常复杂,但是它们使您可以编写看起来很简单的代码。您应该为https://fsharpforfunandprofit.com/series/computation-expressions.htmlhttps://fsharpforfunandprofit.com/series/map-and-bind-and-apply-oh-my.html添加书签,并计划以后再阅读。如果您在第一次,第二次甚至第五次阅读时都不理解它们,请不要担心:这很好。只要继续回到这两篇文章,并在实践中使用诸如gen { ... }gen之类的计算表达式,最终这些概念就会变得清晰起来。每次阅读这些系列文章时,您都会学到更多,而当这一切在大脑中“点击”时,您就会接近启蒙的那一刻。

但是回到您的代码。就像我说的那样,您想使用seq计算表达式。在gen { ... }表达式内,gen { ... }赋值会将let!对象“解包”到生成的Gen<Foo>中,然后可以在其他代码中使用它。您想对Foo int进行哪些操作。因此,我们将在您的代码中包装一个size表达式,并获得以下信息:

gen { ... }

请注意,我还在最后一行添加了let listgen = gen { let! size = sizegen let xs = Gen.listOfLength size let ys = Gen.listOfLength size return (xs, ys) } 关键字。在计算表达式中,return的作用与return相反。 let!关键字包装一个值(类型从let!Gen<Foo>),而Foo关键字包装一个值(类型从returnFoo)。因此,Gen<Foo>行采用了return并将其变成int list * int list。幕后有一些非常复杂的代码,但是在计算表达式的表面上,您只需要考虑“展开”和“包装”类型,即可决定使用Gen<int list * int list>还是{{ 1}}。