当我以惯用方式使用递归处理树时,如何生成F#映射

时间:2014-06-14 14:41:01

标签: recursion map f# immutability

我正在尝试将一系列路径转换为单个树结构,以便以预先确定的格式进行保存。单个路径定义为字符串列表。在保存之前,我正在努力创建树节点的最终集/映射。到目前为止我的代码如下所示。注意可变的节点集合。

如何删除可变性并获得相同的结果?我是否正确地构建了这个或者我是否朝错误的方向前进?作为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

3 个答案:

答案 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