【问题标题】:F#: Recursive collect and filter over N-ary TreeF#:对 N 叉树进行递归收集和过滤
【发布时间】:2011-02-18 09:41:51
【问题描述】:

这伤害了我的大脑!

我想在树结构上进行递归,并将与某个过滤器匹配的所有实例收集到一个列表中。

这是一个示例树结构

type Tree =
| Node of int * Tree list

这是一个测试样本树:

let test = 
    Node((1,
            [Node(2,
                    [Node(3,[]);
                    Node(3,[])]);
            Node(3,[])]))

收集和过滤 int 值为 3 的节点应该会给你这样的输出:

[Node(3,[]);Node(3,[]);Node(3,[])]

【问题讨论】:

    标签: f# n-ary-tree


    【解决方案1】:

    下面的递归函数应该可以解决问题:

    // The 'f' parameter is a predicate specifying 
    // whether element should be included in the list or not 
    let rec collect f (Node(n, children) as node) = 
      // Process recursively all children
      let rest = children |> List.collect (collect f)
      // Add the current element to the front (in case we want to)
      if (f n) then node::rest else rest
    
    // Sample usage
    let nodes = collect (fun n -> n%3 = 0) tree
    

    函数List.collect 将提供的函数应用于 list children - 每个调用都返回一个元素列表和List.collect 将所有返回的列表连接成一个列表。

    您也可以编写(这可能有助于理解代码的工作原理):

    let rest = 
       children |> List.map (fun n -> collect f n)
                |> List.concat
    

    同样的事情也可以用列表推导式来写:

    let rec collect f (Node(n, children) as node) = 
      [ for m in children do 
          // add all returned elements to the result
          yield! collect f m
        // add the current node if the predicate returns true
        if (f n) then yield node ]
    

    编辑:更新代码以返回 kvb 指出的节点。

    顺便说一句:展示一些您迄今为止尝试编写的代码通常是一个好主意。这有助于人们理解你不理解的部分,因此你会得到更有帮助的答案(这也被认为是礼貌的)

    【讨论】:

    • @gradbot:不,他们故意不是。我认为首先了解简单版本是个好主意(在这种情况下,尾递归版本需要继续)。此外,通常可以在没有尾递归的情况下处理 O(log n) 高度(假设树是合理的)。
    • @gradbot: ...但指出这一事实绝对有用。
    • 通常我会在我的问题中显示一些代码,但我的代码在这一点上太远了,我什至没有打扰。我可能会在稍后有时间时尝试实施延续版本。感谢您的帮助!
    【解决方案2】:

    更复杂的尾递归解决方案。

    let filterTree (t : Tree) (predicate : int -> bool) =
        let rec filter acc = function
            | (Node(i, []) as n)::tail -> 
                if predicate i then filter (n::acc) tail
                else filter acc tail
            | (Node(i, child) as n)::tail -> 
                if predicate i then filter (n::acc) (tail @ child)
                else filter acc (tail @ child)
            | [] -> acc
    
        filter [] [t]
    

    【讨论】:

      【解决方案3】:

      只是为了展示F# Sequences Expression 的用法(也许不是最好的方法,我认为托马斯的解决方案可能更好):

      type Tree =
        | Node of int * Tree list
      
      let rec filterTree (t : Tree) (predicate : int -> bool) =
        seq {
          match t with
          | Node(i, tl) ->
              if predicate i then yield t
              for tree in tl do
                yield! filterTree tree predicate 
        }
      
      let test = Node (1, [ Node(2, [ Node(3,[]); Node(3,[]) ]); Node(3,[]) ])
      
      printfn "%A" (filterTree test (fun x -> x = 3))
      

      【讨论】:

      • 您可以通过将 seq {} 替换为 [] 来使其返回列表。
      • @gradbot:谢谢。但这将迫使对序列进行评估,对吗?我只想指出[] 不是Seq{} 的语法糖。
      【解决方案4】:

      当我的大脑因为卡在树上而受伤时,我会尽可能简单明了地说出我想说的话:

      • 给定一棵信息树,列出与谓词匹配的所有子树(在本例中,信息 = 3)。

      一种直接的方法是列出树的所有节点,然后过滤谓词。

      type 'info tree = Node of 'info * 'info tree list
      
      let rec visit = function
          | Node( info, [] )  as node -> [ node ]
          | Node( info, children ) as node -> node :: List.collect visit children
      
      let filter predicate tree = 
          visit tree
          |> List.filter (fun (Node(info,_)) -> predicate info)
      

      这是针对 OP 的样本数据运行的树过滤器:

      let result = filter (fun info -> info = 3) test
      

      让我感到惊讶的一点是,该代码可以轻松地使用适当的谓词处理任何“信息类型”:

      let test2 = 
          Node(("One",
                  [Node("Two",
                          [Node("Three",[Node("Five",[]);Node("Three",[])]);
                          Node("Three",[])]);
                  Node("Three",[])]))
      
      let res2 = filter  (fun info -> info = "Three") test2
      

      或者,如果您想列出信息而不是子树,代码非常简单:

      let rec visit = function
          | Node( info, [] )  -> [ info ]
          | Node( info, children ) -> info :: List.collect visit children
      
      let filter predicate tree = 
          visit tree
          |> List.filter predicate
      

      它支持相同的查询,但只返回'信息记录,而不是树结构:

      let result = filter (fun info -> info = 3) test
      
      > val result : int list = [3; 3; 3; 3]
      

      【讨论】:

        【解决方案5】:

        Tomas 的方法看起来很标准,但与您的示例不太匹配。如果你真的想要匹配节点的列表而不是值,这应该可以工作:

        let rec filter f (Node(i,l) as t) =
          let rest = List.collect (filter f) l
          if f t then t::rest
          else rest
        
        let filtered = filter (fun (Node(i,_)) -> i=3) test
        

        【讨论】:

          【解决方案6】:

          这是一个过度设计的解决方案,但它显示了与Partial Active Patterns 的关注点分离。这不是部分活动模式的最佳示例,但它仍然是一个有趣的练习。匹配语句按顺序计算。

          let (|EqualThree|_|) = function
              | Node(i, _) as n when i = 3 -> Some n
              | _ -> None
          
          let (|HasChildren|_|) = function
              | Node(_, []) -> None
              | Node(_, children) as n -> Some children
              | _ -> None
          
          let filterTree3 (t : Tree) (predicate : int -> bool) =
              let rec filter acc = function
                  | EqualThree(n)::tail & HasChildren(c)::_ -> filter (n::acc) (tail @ c)
                  | EqualThree(n)::tail -> filter (n::acc) tail
                  | HasChildren(c)::tail -> filter acc (tail @ c)
                  | _::tail -> filter acc tail
                  | [] -> acc
          
              filter [] [t]
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2011-08-16
            • 1970-01-01
            • 1970-01-01
            • 2016-07-20
            • 2020-03-07
            • 1970-01-01
            • 1970-01-01
            • 2016-04-19
            相关资源
            最近更新 更多