我是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
答案 0 :(得分:4)
您继续使用相同的参数调用structureCategories
- fullList
和list
。由于参数是相同的,它继续执行与前一个传递完全相同的操作,并最终使用相同的参数再次调用自身。等等。
这是无限递归(“无界”在这里意味着“不知道何时停止重复”),它也不是“尾递归”,所以很自然地,它会导致堆栈溢出。
如果你想把平面列表变成一个树状结构,你可以比这更简单:
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) }
有了这个,你将面临两个问题,我不知道如何解决而不了解你的要求:
ParentId
表示,但是从数据结构中不清楚“空”意味着什么。最后,这个天真的解决方案,虽然比原来的更好,但仍然比它需要的慢一点。它遍历整个列表一次,并且为每个节点执行另一次传递以确定其子节点,从而导致总体复杂度为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
}
}