在遍历具有记录键类型的地图时发生StackOverflow

时间:2018-12-20 10:10:38

标签: f#

我是F#的新手。因此,也许我在这里错过了一些东西。但是,执行以下代码片段会导致StackOverflowException:

type Node = { 
    Position: int*int
    mutable North: Node option 
    mutable South: Node option 
    mutable West:  Node option
    mutable East:  Node option }
let initialNode = { Position = 0, 0; North = None; South = None; West = None; East = None }

let second = { Position = -1, 0; North = None; South = None; West = None; East = None }
initialNode.West <- Some second
second.East <- Some initialNode
let third = { Position = -1, -1; North = None; South = None; West = None; East = None }
second.North <- Some third
third.South <- Some second
let fourth = { Position = -1, 0; North = None; South = None; West = None; East = None }
third.East <- Some fourth
fourth.West <- Some third

let distanceMap = Map.empty |> Map.add initialNode 0
let needVisit = [ initialNode ]

let newDistanceMap =
    (distanceMap, needVisit)
    ||> Seq.fold (fun map node ->
         map |> Map.add node 1)

newDistanceMap |> Map.count

为什么? 如果将映射的键类型更改为记录类型的Position属性(即Tuple),则一切正常。因此,我怀疑问题通常是使用记录作为键类型,还是问题是使用具有mutable属性的记录类型。

2 个答案:

答案 0 :(得分:3)

问题与可变密钥并不直接相关,而是与您拥有周期(初始->秒->初始-> ...)有关。如果注释掉创建周期的行,它将起作用。

就像Alexey一样,您可以通过为您的类型自定义GetHashCode / Equality或更改数据表示形式来解决此问题。

这实际上是不可变数据类型的固有限制:您不能以这种简单的方式表示周期。如果您想在F#中使用此类图并避免过多的可变性,建议您使用其他方法来表示诸如adjacency list之类的图,有关深度分析的更多信息,请尝试搜索归纳图。

答案 1 :(得分:2)

  

因此,我怀疑问题通常是使用记录作为键类型,或者问题是使用具有可变属性的记录类型。

它是引用循环(严格来说,记录本身不需要mutable属性)。要计算记录的哈希码,将使用其所有字段的哈希码。因此,要计算initialNode的哈希码,您需要知道initialNode.West的哈希码,其中使用second,使用second.East,使用{{ 1}},等等...等。

我不确定您不能自定义记录的initialNode方法,尽管我不确定。

请注意,相等比较可能会遇到相同的麻烦(想象首先将GetHashCode()移到末尾)。