在F#中正确使用折叠

时间:2015-10-08 03:19:49

标签: f#

我是F#的新手。我正在尝试使用List.fold帮助我根据Id和ParentId字段生成类别和子类别列表。看起来我可能使这段代码比需要的更复杂,因为我得到了stackoverflow错误。我做错了什么或错过了什么?所有相关的反馈都表示赞赏。

// types
type CategoryStructure = {
    Id: ValidString;
    ParentId: ValidString;
    Name: ValidString;
    Abbreviation: ValidString;
    Description: ValidString;
    SapId: ValidString;
    Section: ValidString;
    SectionPosition: ValidString
}

type DynamicCategories = {
    Category: CategoryStructure;
    SubCategories: seq<DynamicCategories>
}

// this is the function that produces the stack overflow error
let rec private structureCategories (fullList: CategoryStructure list) 
    (list: CategoryStructure list)  =

    List.fold (fun acc elem -> 


                    // get all categories and details
                    let categories = fullList
                    let mainAcc =
                        [

                            for row in categories do
                                if row = elem
                                then
                                    let subs =  
                                        List.fold (fun acc' elem' ->

                                                    if row.Id = elem'.ParentStructureId
                                                    then

                                                        let foundSubCategory = 
                                                            {
                                                                Category = elem';
                                                                SubCategories = structureCategories fullList list |> Seq.ofList
                                                            }
                                                        foundSubCategory :: acc'
                                                    else acc'

                                                    ) List.empty<DynamicCategories> categories
                                        |> Seq.ofList        
                                    yield{
                                        Category = elem;
                                        SubCategories = subs
                                    }


                        ]

                    mainAcc @ acc
                    ) List.empty<DynamicCategories> list  

// this function gets the initial parent categories and calls the above function
let getStructuredCategories () =

        let categories = allCategoriesAndDetails () |> List.ofSeq
        [
            for row in categories do
                if row.ParentStructureId = NotValid
                then yield row
        ] |> structureCategories categories |> Seq.ofList      

2 个答案:

答案 0 :(得分:4)

您继续使用相同的参数调用structureCategories - fullListlist。由于参数是相同的,它继续执行与前一个传递完全相同的操作,并最终使用相同的参数再次调用自身。等等。

这是无限递归(“无界”在这里意味着“不知道何时停止重复”),它也不是“尾递归”,所以很自然地,它会导致堆栈溢出。

如果你想把平面列表变成一个树状结构,你可以比这更简单:

let getChildren fullList parentId = fullList |> List.filter (fun c -> c.ParentId = parentId)
let rec toTree fullList root =
  { Category = root;
    SubCategories = 
      getChildren fullList root.Id 
      |> List.map (toTree fullList) }

有了这个,你将面临两个问题,我不知道如何解决而不了解你的要求:

  1. 如果原始列表恰好有周期,仍会导致堆栈溢出。
  2. 您需要确定树的根是(或者是)。直观地说,这将通过“空”ParentId表示,但是从数据结构中不清楚“空”意味着什么。
  3. 最后,这个天真的解决方案,虽然比原来的更好,但仍然比它需要的慢一点。它遍历整个列表一次,并且为每个节点执行另一次传递以确定其子节点,从而导致总体复杂度为O(N ^ 2)。如果您期望相对较小的列表,这可能没问题,但对于较大的列表则不太好。在这种情况下,我首先将列表转换为哈希表(由ParentId键控),然后使用它来查找子项而不是List.filter

答案 1 :(得分:0)

感谢费奥多尔,我看到了我的错误。关于调用相同的论点,他已经死了。我在foundSubCategory值之前添加了这段代码:

let modifiedList = elem' :: List.empty<CategoryStructure>

然后在后续代码中调用该值:

let foundSubCategory = 
        {
            Category = elem';
            SubCategories = structureCategories fullList modifiedList |> Seq.ofList
        }

这解决了我的问题,但现在正如Fyodor所提到的那样,我现在必须将其重构为更高效的东西。

<强>更新

有鉴于Fyodor指出这是我的代码的当前状态,它取代了原始代码:

let getStructuredCategories ()  =

    let fullList = allCategoriesAndDetails () 

    let parentList () =
        allCategoriesAndDetails ()
        |> Seq.filter (fun p -> p.ParentStructureId = NotValid)

    let rec toTree (fullList': seq<CategoryStructure>) (parent: CategoryStructure) =

        fullList'
        |> Seq.filter (fun x -> x.ParentStructureId = parent.Id)
        |> Seq.map (fun x -> 
                        {
                            Category = x;
                            SubCategories =
                                toTree fullList' x
                        })
    seq {
        for row in parentList () do
        yield {

            Category = row;
            SubCategories = toTree fullList row    
        }

    }