我正在尝试将一系列路径转换为单个树结构,以便以预先确定的格式进行保存。单个路径定义为字符串列表。在保存之前,我正在努力创建树节点的最终集/映射。到目前为止我的代码如下所示。注意可变的节点集合。
如何删除可变性并获得相同的结果?我是否正确地构建了这个或者我是否朝错误的方向前进?作为C#dev,我多年来对F#都很陌生。
type Node = {
Name: string
Parent: Node option
}
let mutable collection = Map.empty
let find parent name =
let key = (parent, name)
if collection.ContainsKey key then
collection.Item key
else
let node = { Name = name; Parent = parent; }
collection <- collection.Add(key, node)
node
let parse path =
let rec gather parent nodes =
match parent with
| None -> nodes
| Some p -> gather p.Parent (p :: nodes)
let rec parserec parent path =
match path with
| [] -> gather parent []
| head :: tail ->
let parent = Some (find parent head)
parserec parent tail
parserec None path
[<EntryPoint>]
let main argv =
let paths = seq {
yield ["A"; "B"; "D"; "E"]
yield ["A"; "B"; "D"; "F"]
yield ["A"; "B"; "D"; "G"]
yield ["A"; "C"; "D"; "G"]
}
let result =
paths
|> Seq.map parse
|> Seq.toList
0 // return an integer exit code
另一组路径可能是:
let paths = seq {
yield ["C:"; "Program Files"; "Common"; "Oracle"]
yield ["C:"; "Program Files"; "Common"; "IBM"]
yield ["C:"; "Program Files"; "Common"; "Microsoft"]
yield ["C:"; "Windows"; "Common"; "Microsoft"]
}
我希望生成一组与此树相当的节点:
C:
Program Files
Common
Oracle
IBM
Microsoft
Windows
Common
Microsoft
答案 0 :(得分:2)
我不完全确定你的算法在做什么,但删除变异是一个相当简单的过程。当你有一个需要&#34;变异&#34; collection
变量,您可以更改它,以便它返回collection
作为结果的一部分。因此,例如您的原始版本find
:
let find parent name =
let key = (parent, name)
if collection.ContainsKey key then
collection.Item key
else
let node = { Name = name; Parent = parent; }
collection <- collection.Add(key, node)
node
原始函数变异collection
,然后返回找到的node
。您可以更改它,以便它返回一个元组,该元组由与节点的新collection
值一起组成。要跟踪当前状态,还需要将collection
作为输入:
let find (collection:Map<_, _>) parent name =
let key = (parent, name)
if collection.ContainsKey key then
collection, collection.Item key // Return a pair with collection and node
else
let node = { Name = name; Parent = parent; }
collection.Add(key, node), node // Return newly extended collection & node
更改其余代码遵循相同的原则 - 添加collection
作为参数并返回新状态作为结果。
答案 1 :(得分:1)
如评论中所述,我宁愿将节点定义为{Name: string; Childs: Node list}
,因为通常会将树从根遍历到分支。
这是一个可能的解决方案:
type Node = {
Name : string
Childs: Node list}
let rec genNodes nodes ls =
match (nodes, ls) with
| (n , [] ) -> n
| ([], x::xs) -> [{Name = x; Childs = genNodes [] xs}]
| ({Name = nm; Childs = ch}::ns, x::xs) ->
if nm = x then {Name = nm; Childs = genNodes ch xs}::ns
else {Name = nm; Childs = ch}::genNodes ns ls
// Test
let paths = seq {
yield ["A"; "B"; "D"; "E"]
yield ["A"; "B"; "D"; "F"]
yield ["A"; "B"; "D"; "G"]
yield ["A"; "C"; "D"; "G"]
}
let result = Seq.fold genNodes [] paths
在这个递归解决方案中,没有可变性,并且通过落到匹配的右侧情况自动执行'find'。
答案 2 :(得分:0)
Tomas的代码&#39;解决方案。还意识到我不需要聚集功能,因为返回地图就足够了。
type Node = {
Name: string
Parent: Node option
}
let parse (collection:Map<_, _>) path =
let find (collection:Map<_, _>) parent name =
let key = (parent, name)
if collection.ContainsKey key then
collection, collection.Item key
else
let node = { Name = name; Parent = parent; }
collection.Add(key, node), node
let rec parserec (collection:Map<_, _>) parent path =
match path with
| [] -> collection
| head :: tail ->
let collection, parent = find collection parent head
parserec collection (Some parent) tail
parserec collection None path
[<EntryPoint>]
let main argv =
let paths = seq {
yield ["C:"; "Program Files"; "Common"; "Oracle"]
yield ["C:"; "Program Files"; "Common"; "IBM"]
yield ["C:"; "Program Files"; "Common"; "Microsoft"]
yield ["C:"; "Windows"; "Common"; "Microsoft"]
}
let result =
paths
|> Seq.fold parse Map.empty
|> Seq.toList
0