3

这伤害了我的大脑!

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

这是一个示例树结构

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,[])]
4

6 回答 6

6

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

// 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将提供的函数应用于列表的所有元素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 指出的节点。

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

于 2010-05-12T00:13:55.857 回答
3

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

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]
于 2010-05-12T01:18:40.070 回答
2

只是为了展示使用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))
于 2010-05-12T00:19:59.323 回答
1

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

  • 给定一棵信息树,列出与谓词匹配的所有子树(在本例中,info = 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]
于 2010-05-14T06:18:50.053 回答
0

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
于 2010-05-12T00:17:30.723 回答
0

这是一个过度设计的解决方案,但它显示了与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]
于 2010-05-12T05:07:58.570 回答