F#:递归树

时间:2009-07-18 18:08:50

标签: f#

我的XElement大致如下:

<Tasks>
  <Task>
    <Parent>0</Parent>
    <Id>1</Id>
  </Task>
  <Task>
    <Parent>1</Parent>
    <Id>2</Id>
  </Task>
  <Task>
    <Parent>1</Parent>
    <Id>3</Id>
  </Task>
  <Task>
    <Parent>3</Parent>
    <Id>5</Id>
  </Task>
  [..]

每个任务元素都有一个唯一的ID,一些我没有报告的信息和一个父ID。父ID指的是另一个任务,因此可以表示树。

我已经有了一个C#函数来对这个结构进行排序:

    private void SortTask(ref XElement taskOutput, XElement tasks, string parent)
    {
        var children = from t in tasks.Elements("Task")
                       where t.Element("Parent").Value == parent
                       select t;

        foreach (XElement task in children.AsEnumerable())
        {
            taskOutput.Add(task);
            SortTask(ref taskOutput, tasks, task.Element("Id").Value);
        }
    }

这里我继续递归调用搜索每个节点的children元素的函数,并将其添加到名为taskOutput的新XElement中。每次我传递对这个新对象的引用,当前元素的id(代表下一次调用中的父元素)和包含所有任务的原始XElement。

现在,我认为这将是一个很好的测试案例,可以学习一下F#,只是以功能方式重写这个东西,但我遇到了麻烦。

这是我到目前为止所得到的:

type TaskManager(taskListXml) = 
    member o.taskList = XElement.Parse(taskListXml).Elements(XName.op_Implicit("Task"))

    member o.Sort =
        let parent =
            o.taskList
            |> Seq.find (fun t -> t.Element(XName.op_Implicit("Parent")).Value.Equals("0"))
        let rec doSort t =
            let parId = t.Element(XName.op_Implicit("Id")).Value
            let children = 
                o.tasklist
                |> Seq.filter (fun x -> x.Element(XName.op_Implicit("Parent")).Value.Equals(parId))
                |> Seq.iter (fun x -> Console.WriteLine(x))
                |> Seq.iter (fun x -> doSort x)

它不编译指定let(在let children)中的返回类型有错误。

任何帮助让我更好地理解这一点? 非常感谢你

4 个答案:

答案 0 :(得分:3)

好的,这是F#中的通用topological sort

// 'parent x y' means y is a child of x
let TopoSort parent s =
    let a = Seq.to_array s
    let visited = Array.create (a.Length) false
    let result = new ResizeArray<_>()
    let rec visit i =
        if not visited.[i] then
            visited.[i] <- true
            result.Add a.[i]
            for j in 0 .. a.Length - 1 do
                if parent a.[i] a.[j] then
                    visit j
    for j in 0 .. a.Length - 1 do
        visit j
    result

这是您的数据

open System.Xml.Linq 
let xn s = XName.op_Implicit s
let xmlString = @"
    <Tasks>
      <Task>
        <Parent>3</Parent>
        <Id>5</Id>
      </Task>
      <Task>
        <Parent>1</Parent>
        <Id>2</Id>
      </Task>
      <Task>
        <Parent>0</Parent>
        <Id>1</Id>
      </Task>
      <Task>
        <Parent>1</Parent>
        <Id>3</Id>
      </Task>
    </Tasks>"
let taskXEs = XElement.Parse(xmlString).Elements(xn("Task"))

然后将TopoSort应用于此问题,您可以将节点“0”隐式地视为“根”,因此我们可以编写

let result = new XElement(xn("Tasks"))
taskXEs 
// prepend faux '0' element to 'root' the toposort
|> Seq.append (Seq.singleton(XElement.Parse("<Task><Parent/><Id>0</Id></Task>")))
|> TopoSort (fun x y -> 
    y.Element(xn("Parent")).Value.Equals(x.Element(xn("Id")).Value))
// remove the faux element
|> Seq.skip 1
|> Seq.iter (fun n -> result.Add(n))

并获得所需的结果:

printfn "%s" (result.ToString())

答案 1 :(得分:2)

这是一个基于你的版本,它似乎可以对子元素进行拓扑排序。但是我想有一个更简单的方法;我现在正在寻找......

open System.Xml.Linq 

let xmlString = @"
    <Tasks>
      <Task>
        <Parent>3</Parent>
        <Id>5</Id>
      </Task>
      <Task>
        <Parent>1</Parent>
        <Id>2</Id>
      </Task>
      <Task>
        <Parent>0</Parent>
        <Id>1</Id>
      </Task>
      <Task>
        <Parent>1</Parent>
        <Id>3</Id>
      </Task>
    </Tasks>
"

let xn s = XName.op_Implicit s

type TaskManager(taskListXml) =     
    member o.taskList = XElement.Parse(taskListXml).Elements(xn("Task"))
    member o.Sort() =
        let xResult = new XElement(xn("Tasks"))
        let parent =
            o.taskList
            |> Seq.find (fun t -> t.Element(xn("Parent")).Value.Equals("0"))
        let rec doSort (t:XElement) =
            let parId = t.Element(xn("Id")).Value
            o.taskList 
            |> Seq.filter (fun x -> x.Element(xn("Parent")).Value.Equals(parId))
            |> Seq.iter (fun x -> 
                xResult.Add(x)
                doSort x
                )
        doSort parent
        xResult

let tm = new TaskManager(xmlString)
let r = tm.Sort()
printfn "%s" (r.ToString())

答案 2 :(得分:1)

您的doSort函数不会返回任何内容。 (甚至不是unit,这相当于C#中的void方法。仅在F#中的函数中定义变量是无效的。

此外,我不确定你是否真的要为children变量分配任何内容,因为你根本没有使用它。尝试将doSort功能更改为:

let rec doSort t =
    let parId = t.Element(XName.op_Implicit("Id")).Value
    o.tasklist
        |> Seq.filter (fun x -> x.Element(XName.op_Implicit("Parent")).Value.Equals(parId))
        |> Seq.iter (fun x -> Console.WriteLine(x))
        |> Seq.iter (fun x -> doSort x)

答案 3 :(得分:1)

这是一个老帖子,但没有看到任何解决堆栈溢出问题。

对于任何想知道的人,可以通过使用尾递归来避免堆栈溢出。确保递归调用是函数执行的最后一个操作,例如在匹配结束时或者如果是分支,函数的最后等等。

注意不要以任何方式,形状或形式使用递归调用的结果,包括“num +(recCall val)”,因为这需要执行以跳回到原始功能来执行总和。这是跳跃,或更恰当地,记住何时何地跳转到堆栈溢出,如果没有任何东西可以回来做,编译器可以自由地消除额外的开销。

这是为什么如此多的Seq和List函数(例如Seq.unfold)需要累加器/状态参数的原因之一。它允许您通过在下一次调用开始时处理它来安全地对先前递归的结果执行操作。

例如:

会在尾部位置溢出:num + (recCall val)

不会在尾部位置溢出:(recCall num val)