[英]Proper use of fold in 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
您繼續使用相同的參數( fullList
和list
調用structureCategories
。 由於參數相同,因此它將繼續執行與上一遍完全相同的操作,並最終以相同的參數再次調用自身。 等等。
這是無界遞歸(“ unbounded”在這里表示“不知道何時停止遞歸”),它也不是“尾遞歸”,因此很自然地,它會導致堆棧溢出。
如果要將平面列表轉換為樹形結構,可以執行以下操作:
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
來表示,但是從您的數據結構尚不清楚“ empty”是什么意思。 最后,這種幼稚的解決方案雖然比原始解決方案要好,但仍然比需要的要慢一些。 它遍歷整個列表一次,並且對每個節點進行另一遍以確定其子級,從而導致O(N ^ 2)的整體復雜性。 如果您希望列表相對較小,則可能會很好,但對於較大列表,則不太好。 在這種情況下,我將首先將列表轉換為哈希表(由ParentId
鍵入鍵),然后使用該表而不是List.filter
查找子List.filter
。
多虧費奧多爾(Fyodor),我看到了我的錯誤。 他對提出同樣的論點一無所知。 我在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
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.