【问题标题】:F# sequence comparisonF# 序列比较
【发布时间】:2013-06-14 04:54:39
【问题描述】:

我已经实现了一个斐波那契数列生成器,如下所示

let getNext upperLimit current= 
        let (e1, e2) = current
        let next = e1 + e2
        if next > upperLimit then None
        else Some (next, (e2,next))

let fib upperLimit = (0,1) |> Seq.unfold (getNext upperLimit) |> Seq.append [0;1] 

我的测试代码是

[<Test>]
member Spec.``fib not exeeding 20 should be 0,1,1,2,3,5,8,13``()=
    let expected = seq [0;1;1;2;3;5;8;13] 
    let result = fib 20
    let expectedSameAsResult = (expected = result)
    printfn "Expected: %A  Result: %A result length: %d" expected result (Seq.length result) 
    Assert.That expectedSameAsResult

测试失败,打印结果为

预期:[0; 1个; 1个; 2; 3; 5个; 8个; 13] 结果:seq [0; 1个; 1个; 2; ...] 结果长度:8

当我使用 for 循环打印结果中的每个元素时,我得到了预期序列中完全相同的元素。

那么,期望序列和结果序列有什么区别?

编辑:我的实现可以在https://github.com/weima/EulerProblems/tree/master/EulerProblems找到

编辑:回答 John Palmer 的回答 我刚刚在 F# 交互窗口中写了一个测试

 let a = seq[1;2;3]
 let b = seq[1;2;3]
 let c = a = b;;

我得到的结果是 val a : seq = [1; 2; 3] val b : seq = [1; 2; 3] val c : bool = true

所以 F# 也可以对序列进行结构比较。

编辑以反映 Gene Belitski 的回答 我已将测试更改为

[<Test>]
member Spec.``fib not exeeding 20 should be 0,1,1,2,3,5,8,13``()=
    let expected = seq [0;1;1;2;3;5;8;13] 
    let result = Problem2.fib 20
    let comparedResult =  Seq.compareWith (fun a b -> a - b) expected result  
    let expectedSameAsResult = (comparedResult = 0)
    Assert.That expectedSameAsResult

它现在起作用了。谢谢!但我仍然不明白为什么简单的 seq[1;2;3]=seq[1;2;3] 有效,但我的测试用例却没有。

【问题讨论】:

  • 你已经受到编译器没有按照你的预期做的影响 - 试试a=(b|&gt; Seq.map id);;
  • @WeiMa:要了解发生了什么,请尝试在 FSI 中使用真正序列 seq {1..3} = seq {1..3} 的一些不同的简单案例 - 这个表达式等于 false
  • 谢谢各位,我知道了。

标签: f#


【解决方案1】:

虽然您可能期望 a=b 会比较序列的元素,但实际上 a=b 会计算引用相等性。

你可以用类似的东西看到这个

seq {1..3} = seq {1..3}

返回 false。

但是,在某些情况下,当您使用常量时,您会得到令人困惑的结果,尤其是

seq [1;2;3] = seq [1;2;3]

返回 true,这令人困惑。

要避免这个问题,你需要做类似的事情

 let test a b = Seq.fold (&&) true (Seq.zip a b |> Seq.map (fun (aa,bb) -> aa=bb))

比较元素。

或者,您可以使用Seq.compareWith,如 Gene 的回答中所述。但是,这要求元素还实现比较运算符和相等性,这对于一些实现 = 但不比较的可区分联合可能不是这样。

【讨论】:

  • 在他的博客blogs.msdn.com/b/dsyme/archive/2009/11/08/…Don Syme 中说 f# 支持“结构”相等,我假设比较两个序列属于这一类
  • @WeiMa 这适用于list,但不适用于Seq
  • 我认为listSeq 的差异是由于list 具有固定大小,而Seq 可能具有不确定的大小(您将如何比较无限序列?)。
  • 序列不通过引用相等比较。 Equals 方法通常由= 的通用实现使用(这里有一些细微之处——例如,数组被特殊处理)。对于列表,Equals 被覆盖以实现结构相等,但对于其他类型的序列,实现可能会退回到默认的引用相等(或做其他事情)。
【解决方案2】:

添加到约翰的答案:序列相等可以通过Seq.compareWith 库函数确定:

let compareSequences = Seq.compareWith Operators.compare

那么序列相等就是表达式的值

let expectedSameAsResult = (compareSequences expected result = 0)

【讨论】:

    【解决方案3】:

    Operators.seq<'T> 函数的 MSDN 说:使用序列表达式语法构建序列。 如果您查看its implementation,您会发现它基本上只是标识函数,仅在与序列表达式语法一起使用时对编译器具有特殊意义。如果您使用 list 调用 - 您将得到相同的列表(向上转换为 seq<_>)。

    根据 F# 规范重新构造平等:

    默认情况下,记录、联合和结构类型定义(称为结构类型)隐式包括 编译器生成的结构相等、散列和比较的声明。这些隐式声明 由以下结构相等和散列组成:

    override x.GetHashCode() = ...
    override x.Equals(y:obj) = ...
    interface System.Collections.IStructuralEquatable with 
        member x.Equals(yobj: obj, comparer: System.Collections.IEqualityComparer) = ...
        member x.GetHashCode(comparer: System.IEqualityComparer) = ...
    

    以下声明可以进行结构比较:

    interface System.IComparable with 
        member x.CompareTo(y:obj) = ...
    interface System.Collections.IStructuralComparable with 
        member x.CompareTo(yobj: obj, comparer: System.Collections.IComparer) = ...
    

    对于异常类型,会生成结构相等和散列的隐式声明,但不会生成结构比较的声明。永远不会为接口、委托、类或枚举类型生成隐式声明。枚举类型通过它们作为整数的底层表示隐式地支持相等、散列和比较

    所以列表(本质上是联合) - 支持结构相等和序列 - 不支持。要成对检查元素,您还可以使用 Seq.forall2

    let isEqual = (s1, s2) ||> Seq.forall2 (=)
    

    【讨论】:

    • Seq.forall2 不能满足严格结构相等性的要求,尤其是在两个序列长度不同的情况下。 Seq.forall2 (=) [1;2;3] [1;2;3;4] ;; val it : bool = true
    【解决方案4】:

    对于任何找到此答案的人只想用一种简单的方法来比较两个序列,可以使用 Seq.compareWith 的替代方法:

    有一个 .NET 方法 Enumerable.SequenceEqual。我在测试时一直使用它。

    示例用法:

    let sequenceA = seq { 1..5 }
    let sequenceB = seq { 1..5 }
    
    Enumerable.SequenceEqual (sequenceA, sequenceB) // True
    

    【讨论】:

      【解决方案5】:

      原因:

      序列不支持结构相等性。

      如果您将seq 视为.NET IEnumerable&lt;T&gt;,也许这更有意义?这里seq [1;2;3] = seq [1;2;3] 是一个不幸的巧合。考虑一个非纯的seq

      let rnd = System.Random()
      let x = seq { yield rnd.Next() }
      printfn "x is %A" x
      printfn "x is %A" x
      

      结果:

      x is seq [372511654]
      x is seq [1026368248]
      
      val rnd : System.Random
      val x : seq<int>
      

      显而易见的答案:

      在此处使用list 而不是seq

      [<Test>]
      member Spec.``fib not exeeding 20 should be 0,1,1,2,3,5,8,13``()=
          let expected = [0;1;1;2;3;5;8;13] 
          let result = fib 20 |> Seq.toList
          let expectedSameAsResult = (expected = result)
      

      详情:

      查看其他答案。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2016-08-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-04-27
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多