我正在尝试解决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嵌套级别的嵌套结构上运行缓慢,但没有堆栈溢出。
答案 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以及其他所有内容来递归。