Next up: adding this to the semantic network code.
As always, presented without warranty or implied fitness. That and there may still be a bug or two or a simpler way of doing this.
The functions:
module Searcher
open System.Collections.Generic
// This is the actual tail-recursive function.
// Note: not tail-recursive unless tail-call is turned on.
let rec internal search0<'a,'b> (fGetEnum:'a->IEnumerator<'a> option)
                                (fTest:'a->bool)
                                (enum:IEnumerator<'a>)
                                (fFail:unit->'a list) 
                                (fSucceed:'a->'a list->'a list) =
  // This function is a direct tail recursion 
  // for each item in a sequence.
  match enum.MoveNext() with
  | false -> fFail()
  | _ -> 
      let current = enum.Current
      // Test for goal node.
      match fTest current with
      | true -> fSucceed current []
      | false -> 
          // Test for child enumeration.
          // (Can user short circuit here.
          match (fGetEnum current) with 
          | None -> fFail()
          | Some(newEnum) ->
              // Direct tail recursion plus
              // a continuation tail recursion.
              // (Formatted for ease of reading.)
              (search0 
                fGetEnum
                fTest
                enum 
                (fun ()->
                   search0 
                     fGetEnum
                     fTest
                     newEnum 
                     fFail 
                     // Composition of list.
                     (fun a0 al->(fSucceed current (a0::al))))
                 fSucceed)
// Interface funcion.             
and search<'a,'b> (fGetEnum:'a->IEnumerator<'a> option)
                  (fTest:'a->bool)
                  (a:'a) =
  // Test on the root node here makes
  // search0 more straightforward.
  match fTest a with 
  | true -> [a]
  | false ->
      // Test for child enumeration.
      // (Can user short circuit here.
      match (fGetEnum a) with 
      | None -> []
      | Some(newEnum) ->
          (search0 
            fGetEnum
            fTest
            newEnum
            (fun ()->[]) 
            (fun a0 al->a::a0::al))
Test:
open System.Collections.Generic
// A simple tree node based on a HashSet.
[<System.Diagnostics.DebuggerDisplayAttribute("{S}")>]
type Node (s:string) =
  
  let h = new HashSet<Node>()
  
  member this.Add n =
    h.Add(n) |> ignore
    this // Convenient chaining of adds.
  
  member this.H = h
  member this.S = s 
// This "safe" search uses a hash table 
// to detect circularity and returns 
// a None for the enumerator option.
let searchSafe =
  // Hash of visited nodes.
  let tested = new HashSet<Node>()
  
  // Returns child node enumerator iff
  // node has not already been considered.
  // Also marks unvisited nodes as visited.
  let fEnum (n:Node) =
    match tested.Add(n) with
    | false -> None
    | true -> Some(n.H.GetEnumerator():>IEnumerator<Node>)
  
  // Return a function.
  // (F# behavior means tested Hash will be cached.)
  (fun n s ->
    tested.Clear()  
    Searcher.search 
      fEnum
      (fun (n:Node)->n.S=s)
      n)
// Test it. 
// (Formatted for blog width.)
let n11 = (Node "n11").Add(Node "n110").Add(Node "n111")
let n10 = (Node "n10").Add(Node "n100").Add(Node "n101")
let n01 = (Node "n01").Add(Node "n010").Add(Node "n011")
let n00 = (Node "n00").Add(Node "n000").Add(Node "n001")
let n1  = (Node "n1").Add(n10).Add(n11)
let n0  = (Node "n0").Add(n00).Add(n01)
let n   = (Node "n").Add(n0).Add(n1) 
let l     = searchSafe n "n"
let l011  = searchSafe n "n011"
let l01x  = searchSafe n "n01x"
printfn "Your breakpoint here!"  
 
 
 

 
No comments:
Post a Comment