99个Haskell问题。 F#中的#7 - stackoverflowexception

时间:2013-11-04 20:52:59

标签: f#

我正在尝试解决F#中99个Haskell问题的任务。 任务#7看起来很简单,解决方案可以在很多地方找到。除了我通过谷歌搜索尝试找到的前几个解决方案(例如https://github.com/paks/99-FSharp-Problems/blob/master/P01to10/Solutions.fs)是错误的。

我的例子非常简单。 我正在尝试构建非常深的嵌套结构并将其折叠

type NestedList<'a> =
| Elem of 'a 
| NestedList of NestedList<'a> list

let flatten list = 
    // 
    (* StackOverflowException   
    | Elem(a) as i -> [a]                    
    | NestedList(nest) -> nest |> Seq.map myFlatten |> List.concat
    *)
    // Both are failed with stackoverflowexception too https://github.com/paks/99-FSharp-Problems/blob/master/P01to10/Solutions.fs

let insideGen count = 
    let rec insideGen' count agg = 
        match count with 
        | 0 -> agg
        | _ -> 
            insideGen' (count-1) (NestedList([Elem(count); agg]))
    insideGen' count (Elem(-1))

let z = insideGen 50000
let res = flatten z

我试图用CPS风格重写解决方案,但是我做错了什么或者看错了方向 - 我尝试过的一切都没有用。

有任何建议吗?

P.S。 Haskell解决方案,至少在具有50000嵌套级别的嵌套结构上运行缓慢,但没有堆栈溢出。

2 个答案:

答案 0 :(得分:3)

这是一个CPS版本,不会使用您的测试溢出。

let flatten lst = 
  let rec loop k = function
    | [] -> k []
    | (Elem x)::tl -> loop (fun ys -> k (x::ys)) tl
    | (NestedList xs)::tl -> loop (fun ys -> loop (fun zs -> k (zs @ ys)) xs) tl
  loop id [lst]

修改

更可读的方式是:

let flatten lst = 
  let results = ResizeArray()
  let rec loop = function
    | [] -> ()
    | h::tl -> 
      match h with
      | Elem x -> results.Add(x)
      | NestedList xs -> loop xs
      loop tl
  loop [lst]
  List.ofSeq results

答案 1 :(得分:1)

免责声明 - 我不是一个很深的F#程序员,这不会是惯用的。 如果您的堆栈溢出,则意味着您没有尾递归解决方案。这也意味着您选择使用堆栈内存进行状态。传统上,您希望将堆内存交换为堆栈内存,因为堆内存的供应量相对较大。所以诀窍是建模堆栈。

我要定义一个堆栈的虚拟机。每个堆栈元素将是一个状态块,用于遍历列表,该列表将包括列表和程序计数器,该计数器是要检查的当前元素,并且将是NestedList<'a> list * int的元组。该列表是正在遍历的当前列表。 int是列表中的当前位置。

type NestedList<'a> =
    | Elem of 'a
    | Nested of NestedList<'a> list

let flatten l =
    let rec listMachine instructions result =
        match instructions with
        | [] -> result
        | (currList, currPC) :: tail ->
            if currPC >= List.length currList then listMachine tail result
            else
                match List.nth currList currPC with
                | Elem(a) -> listMachine ((currList, currPC + 1 ) :: tail) (result @ [ a ])
                | Nested(l) -> listMachine ((l, 0) :: (currList, currPC + 1) :: instructions.Tail) result
    match l with
    | Elem(a) -> [ a ]
    | Nested(ll) -> listMachine [ (ll, 0) ] []

我做了什么?我编写了一个尾递归函数,它运行“Little Lisper”样式代码 - 如果我的指令列表为空,则返回我的累积结果。如果没有,请在堆栈顶部操作。我将一个便利变量绑定到顶部,如果PC在最后,我将使用当前结果递归到堆栈的尾部(pop)。否则,我会查看列表中的当前元素。如果它是一个Elem,我递归,推进PC并将Elem添加到列表中。如果它不是elem,我会通过推送一个带有NestedList的新堆栈,然后是当前的堆栈元素,并将PC提前1以及其他所有内容来递归。