F #: рекурсивный сбор и фильтрация по N-арному дереву - PullRequest
3 голосов
/ 12 мая 2010

Это вредит моему мозгу!

Я хочу просмотреть древовидную структуру и собрать все экземпляры, которые соответствуют какому-либо фильтру, в один список

Вот пример структуры дерева

type Tree =
| Node of int * Tree list

Вот дерево тестового образца:

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

Сбор и фильтрация по узлам со значением 3 и int должны дать вам следующий вывод:

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

Ответы [ 6 ]

6 голосов
/ 12 мая 2010

Следующая рекурсивная функция должна помочь:

// 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.

Кстати: обычно неплохо показать код, который вы пытались написать до сих пор. Это помогает людям понять, какую часть вы не поняли, и поэтому вы получите более полезные ответы (и это также считается вежливым)

3 голосов
/ 12 мая 2010

Более сложное хвостовое рекурсивное решение.

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]
2 голосов
/ 12 мая 2010

Просто для демонстрации использования 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))
1 голос
/ 14 мая 2010

Когда у меня болит мозг, потому что он застрял в дереве, я стараюсь говорить то, что хочу, как можно проще и яснее:

  • Учитывая дерево информации, перечислите все поддеревья, соответствующие предикату (в данном случае, 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]
0 голосов
/ 12 мая 2010

Здесь представлено слишком сложное решение, но оно показывает разделение проблем с Частично активными образцами . Это не лучший пример для частичных активных паттернов, но, тем не менее, это было забавное упражнение. Заявления о совпадении оцениваются по порядку.

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]
0 голосов
/ 12 мая 2010

Подход Томаса выглядит стандартно, но не совсем соответствует вашему примеру. Если вы действительно хотите список совпадающих узлов, а не значений, это должно работать:

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
...