Seiten

Mittwoch, 9. Februar 2011

F#. A* (a-star) Pathfinding Algorithm with Priority Queue and Finger Tree.

Update A* Star Pathfinding with Jump Point Search.
// Astar.fs
//from Haskell version http://www.haskell.org/haskellwiki/Haskell_Quiz/Astar/Solution_Dolio
namespace Astar

module AstarTypes =
    type Point = int * int
    type Map = char list list

    let inline flip f b a = f a b

[<RequireQualifiedAccess>]
module PriorityQueue =
  exception Empty

  type t<'k,'a> =
    | E
    | T of 'k * 'a * t<'k, 'a> * Lazy<t<'k,'a>>

  let empty = E

  let isEmpty = function E -> true | _ -> false

  let inline singleton prio x = T(prio, x, E, lazy E)

  let rec merge t1 t2 =
    match t1, t2 with
    | E, h -> h
    | h, E -> h
    | T(xprio, _, _, _), T(yprio, _, _, _) ->
        if xprio <= yprio then link t1 t2 else link t2 t1

  and link t1 t2 =
    match t1, t2 with
    | T(prio, a, E, m), r -> T(prio, a, r, m)
    | T(prio, a, t, m), r -> T(prio, a, E, lazy merge (merge r t) (m.Force()))
    | _ -> failwith "should not get there"

  let inline insert prio x q = merge (singleton prio x) q

  let rec contains prio = function
    | E -> false
    | T (sndPrio, _, a, b) ->
        prio = sndPrio || contains prio a || contains prio (b.Force())

  let deleteFindMin = function
    | E -> raise Empty
    | T(prio, a, t, m) ->(prio, a), merge t (m.Force())
  
  let inline findMin q = fst (deleteFindMin q)

  let inline deleteMin q = snd (deleteFindMin q)


  let rec remove x = function
    | E -> E
    | T(prio, y, a, b) as t ->
        if a = x
        then merge a (b.Force())
        else T(prio, y, remove x a, lazy remove x (b.Force()))

  let inline ofSeq s = Seq.fold (fun q (prio, a) -> merge (singleton prio a) q) empty s

module AstarImpl = 
  //Point -> (Point -> Set<Point>) -> (Point -> bool) -> (Point -> int) -> (Point -> int) -> Point list
  let astar start succ finish cost heur =
      let rec inner seen q =
           match PriorityQueue.isEmpty q with
           | true -> failwith "No Solution."
           | false ->
               let ((c, next), dq) = PriorityQueue.deleteFindMin q
               let n = List.head next

               match finish n with
               | true -> next
               | otherwise -> 
                   let succs = succ n

                   let costs item = c + (cost item) + (heur item) - (heur n) 
                   
                   let q' = 
                       Set.difference succs seen |> Seq.map (fun x ->costs x, x :: next) 
                       |> PriorityQueue.ofSeq |> PriorityQueue.merge dq

                   inner (Set.union seen succs) q'
      inner (Set.singleton start) (PriorityQueue.singleton (heur start) [start])
Version mit FingerTree aus dem Beitrag.
//Astar.fs
...
module AstarFtree =
  open FingerTree
  
  type PrioMonoid () =
        interface IMonoid<int> with
            member inline this.Zero = System.Int32.MaxValue
            member inline this.Plus a b = min a b
  
  type PrioElement =
    {Prio : int; Val : AstarTypes.Point list} with
    static member inline ofPair (p, v) = {Prio = p;Val = v}
    interface IMeasured<int> with
        member inline this.Value = this.Prio

  type FingerAstar = 
      {Tree : FingerTree<PrioElement, int, PrioMonoid>;
       Seen : Set<AstarTypes.Point>} with
          member inline this.deleteFindPrio =
                match this.Tree with
                | FingerTree.Empty -> failwith "tree is empty."
                | FingerTree.Single b -> b, FingerTree.Empty
                | FingerTree.Deep (v, _,_,_) ->                    
                    match FingerTree.findAndSplit (fun x -> x = v) this.Tree with
                    | Some (Split(l, x, r)) -> x, FingerTree.concat l r

          static member inline ofSeq s =
              {Tree = Seq.fold (AstarTypes.flip FingerTree.push_front) FingerTree.Empty s
               Seen = Set.empty}
  
  //Point -> (Point -> Set<Point>) -> (Point -> bool) -> (Point -> int) -> (Point -> int) -> Point list
  let astar start succ finish cost heur =
      let rec inner q  =
           match FingerTree.isEmpty q.Tree with
           | true -> failwith "No Solution."
           | false ->
               let (element, dq) = q.deleteFindPrio
               let n = List.head element.Val

               match finish n with
               | true -> element.Val
               | otherwise -> 
                   let succs = succ n

                   let costs item = element.Prio + (cost item) + (heur item) - (heur n) 
                   
                   let q' = 
                       Set.difference succs q.Seen 
                       |> Seq.map (fun x ->costs x, x :: element.Val)
                       |> Seq.map PrioElement.ofPair 
                       |> FingerAstar.ofSeq 
                       
                   inner {Tree = FingerTree.concat dq q'.Tree
                          Seen = Set.union q.Seen succs}
      inner {Seen = Set.singleton start; 
             Tree = FingerTree.Single {Prio = heur start; Val = [start]} }

//Programm.fs 
open Astar
open System

 //Point -> Point -> int
let inline heuristic (x, y) (u, v) = max (abs (x - u))  (abs (y - v))

// Map -> Point -> Set<Point> 
let inline successor m (x,y) = 
    set[for u in  [x + 1; x; x - 1] do
        for v in  [y + 1; y; y - 1] do
        if (0 <= u && u < List.length m 
            && 0 <= v && v < List.length (List.head m)) 
            && (u <> x || y <> v) 
            && (List.nth (List.nth m u) v <> '~') then
            yield set [u, v]
        ]
    |> Set.unionMany

//char -> Map -> Point
let inline find c =
      let rec inner x m = 
          match m with
          | [] ->  failwith "Can't find tile."
          | h :: t -> 
              match List.tryFindIndex (fun item -> item = c) h with
              | Some y -> x, y
              | otherwise -> inner (x+1) t
      inner 0
// char list list -> Point list -> char list list
let inline path m l = 
       List.mapi (fun idx ht ->
           List.mapi (fun idy c->
               if List.exists (fun (n', m') -> (n', m') = (idx, idy)) l then '#' else c) ht) m

let inline run s fAstar =
      let m = List.map (fun (str : string) ->List.ofSeq str) s
      let start = find 'S' m
      let finish = find 'F' m
      let succ = successor m
      let h     = heuristic finish
      let cost (x, y) = 
          let costs = Map.ofList [('S',1);('F',1);('.',1);('*',2);('^',7)]
          List.nth m x 
          |> AstarTypes.flip List.nth y
          |> AstarTypes.flip Map.find costs

      path m (fAstar start succ ((=) finish) cost h)

let input =
    [ "..*..S";
     "*^*^~.";
     "*~*^.~";
     "^^^.~^";
     "^~^~.~";
     "~~^~~.";
     "F*~*~~";]
printfn "Input Map :" 
List.iter (fun x -> printfn "%A" (List.ofSeq x))  input
let res = run input AstarImpl.astar
printfn " Path "
List.iter (fun x -> printfn "%A" x)  res

let resFtree = run input AstarFtree.astar
printfn " Path Finger Tree"
List.iter (fun x -> printfn "%A" x)  resFtree

Input Map :
['.'; '.'; '*'; '.'; '.'; 'S']
['*'; '^'; '*'; '^'; '~'; '.']
['*'; '~'; '*'; '^'; '.'; '~']
['^'; '^'; '^'; '.'; '~'; '^']
['^'; '~'; '^'; '~'; '.'; '~']
['~'; '~'; '^'; '~'; '~'; '.']
['F'; '*'; '~'; '*'; '~'; '~']
 Path
['.'; '.'; '*'; '.'; '.'; '#']
['*'; '^'; '*'; '^'; '~'; '#']
['*'; '~'; '*'; '^'; '#'; '~']
['^'; '^'; '^'; '#'; '~'; '^']
['^'; '~'; '#'; '~'; '.'; '~']
['~'; '~'; '#'; '~'; '~'; '.']
['#'; '#'; '~'; '*'; '~'; '~']
 Path Finger Tree
['.'; '.'; '*'; '.'; '.'; '#']
['*'; '^'; '*'; '^'; '~'; '#']
['*'; '~'; '*'; '^'; '#'; '~']
['^'; '^'; '^'; '#'; '~'; '^']
['^'; '~'; '#'; '~'; '.'; '~']
['~'; '~'; '#'; '~'; '~'; '.']
['#'; '#'; '~'; '*'; '~'; '~']

Hier habe ich den A Star Algorithmus verwendet, um eine Labyrinth Lösung zu finden.

Dienstag, 1. Februar 2011

F#. Net Regex vs. DFA Table.

Um den im letzten Beitrag erstellten DFA sinnvoll einsetzen zu können, sollten wir die Liste von Transitions in einer Tabelle (2D Array) umwandeln. Dann können wir zu jedem Symbol des Alphabets und dem aktuellen Zustand den nächsten Zustand ermitteln, wobei das Symbol als Array-Index verwendet wird.
    let nextState = table.[int 'a'].[currentState]
//Program.fs
open System
open System.Text.RegularExpressions
open System.Text
open Graph
open RegExParsing
open RegExCompiling
open RegExProcessor
open ConvertNfaToDfaTable
open Microsoft.FSharp.Collections 

type DfaTableContext = { table : int[][];
                         accept : Set<Node>; //DFA accept states
                         start : Node;
                         numberofState : int;
                         fstLetter : int
                         }

let inline tabulate f size = Array.init size (fun i-> f i)

let inline createDFATable (context : ConvertContext)  =
    let fstLetter = int (List.head context.alphabet)
    let fillArr arr key transitions  = 
        Seq.fold (fun (acc : int[][]) (Transition (fromNode, toNode, _)) -> 
                 match Set.contains fromNode context.accept with
                 | false -> 
                     acc.[int key - fstLetter].[fromNode] <- toNode
                     acc
                 | true -> 
                     acc.[int key - fstLetter].[fromNode] <- fromNode
                     acc) arr transitions
    let tbl = 
        let initTable = 
            tabulate (fun _ -> Array.create context.nextNode context.start)
                (List.length context.alphabet)
        
        Seq.groupBy (fun (Transition (_, _, (Simple c))) -> c) context.trans
        |> ;Seq.fold (fun (acc : int[][]) (key, transitions) ->
                fillArr acc key transitions
                ) initTable

    {table = tbl; accept = context.accept; start = context.start;
     numberofState = context.nextNode; fstLetter = fstLetter}
Letztendlich geht es um die Anwendung vom regulären Ausdruck in dem Fall von der String-Verkettung. Die .Net Regex muss nach jede Verkettung die gesamte neu entstandene Zeichenfolge komplett durchgehen. Im Gegensatz dazu können die resultierende Zustand-Arrays in dem Fall vom DFA ganz einfach zusammengesetzt werden.

Jetzt können wir die Match-Funktion schreiben.
let inline foldUntil dfa (input:string) length = 
      let rec inner acc pos  =
          match pos = length with
          | true -> acc, false
          | _ ->
              let idx = int (input.Chars pos)
              //compose state arrays.
              let res = Array.map (fun node-> dfa.table.[idx - dfa.fstLetter].[node]) acc
              //checking if initial state 0 maps the to the one accepted final state
              match Set.contains res.[0] dfa.accept with
              | true -> res, true
              | false -> inner res (pos+1) 
      inner    
  
let inline matchInput dfa input =   
      foldUntil dfa input input.Length (tabulate id dfa.numberofState) 0 

//Simulate a stream.
let inline streamInput size =
      let str = String.Concat( Array.create size " Match Me " )
      seq{
          yield str+"("
          yield! seq{for i in 1..20 -> str}
          yield str+"007"
          yield str+"bb"
          yield str+")"
          }

let inline test f =
    printfn "Test Start"
    let sw = new System.Diagnostics.Stopwatch()
    sw.Start()
    f()
    sw.Stop()
    printfn "Time Duration : %A" sw.ElapsedMilliseconds

let inline testRegexWithStream nfa regex size letters =
    let dfaContext = convert letters nfa |> createDFATable
    let regex = new Regex (regex)
    let builder = StringBuilder()

    printfn "Array Size %A" size
    let matchStream stream=  
        Seq.fold (fun (acc : int []) x-> 
            let tbl, isMatch = matchInput dfaContext x
            if isMatch then
                printfn "match: true, %A" tbl
                tbl
            else
                let res = Array.map (fun node -> tbl.[node]) acc
                printfn "match: %A, table: %A" (Set.contains res.[0] dfaContext.accept) res
                res) (tabulate id dfaContext.numberofState) stream
    test (fun ()-> 
        printfn "Stream with DFA Table." 
        (streamInput  size |> matchStream ) |> ignore)
    
    let matchStreamRegex stream = 
        stream |> Seq.iter (fun (item: string) ->
            try
                let input = builder.Append(item).ToString() 
                printfn "Input Size: %A; match: %A" builder.Length (regex.Match(input).Success)
            with
                | :? System.ArgumentOutOfRangeException -> printfn "input to big for StringBuilder!"
                | :? System.OutOfMemoryException ->  printfn "input to big for StringBuilder!") 

let run () = 
    let regex = "aa|bb"
    let letters =[' '..'z']
    let nfa = 
        regex |> RegExParsing.parseRegExp |> RegExCompiling.compile FullMatch
        
    testRegexWithStream nfa regex 2000 letters
    testRegexWithStream nfa regex 700000 letters

run()

Array Size 2000
Test Start
Stream with DFA Table.
match: false, table: [|0; 0; 0; 3; 4|]
match: false, table: [|0; 0; 0; 3; 4|]
match: false, table: [|0; 0; 0; 3; 4|]
match: false, table: [|0; 0; 0; 3; 4|]
match: false, table: [|0; 0; 0; 3; 4|]
match: false, table: [|0; 0; 0; 3; 4|]
match: false, table: [|0; 0; 0; 3; 4|]
match: false, table: [|0; 0; 0; 3; 4|]
match: false, table: [|0; 0; 0; 3; 4|]
match: false, table: [|0; 0; 0; 3; 4|]
match: false, table: [|0; 0; 0; 3; 4|]
match: false, table: [|0; 0; 0; 3; 4|]
match: false, table: [|0; 0; 0; 3; 4|]
match: false, table: [|0; 0; 0; 3; 4|]
match: false, table: [|0; 0; 0; 3; 4|]
match: false, table: [|0; 0; 0; 3; 4|]
match: false, table: [|0; 0; 0; 3; 4|]
match: false, table: [|0; 0; 0; 3; 4|]
match: false, table: [|0; 0; 0; 3; 4|]
match: false, table: [|0; 0; 0; 3; 4|]
match: false, table: [|0; 0; 0; 3; 4|]
match: false, table: [|0; 0; 0; 3; 4|]
match: true, [|4; 4; 4; 3; 4|]
match: true, table: [|4; 4; 4; 3; 4|]
Time Duration : 127L
Test Start
Stream with .Net Regex
Input Size: 20001; match: false
Input Size: 40001; match: false
Input Size: 60001; match: false
Input Size: 80001; match: false
Input Size: 100001; match: false
Input Size: 120001; match: false
Input Size: 140001; match: false
Input Size: 160001; match: false
Input Size: 180001; match: false
Input Size: 200001; match: false
Input Size: 220001; match: false
Input Size: 240001; match: false
Input Size: 260001; match: false
Input Size: 280001; match: false
Input Size: 300001; match: false
Input Size: 320001; match: false
Input Size: 340001; match: false
Input Size: 360001; match: false
Input Size: 380001; match: false
Input Size: 400001; match: false
Input Size: 420001; match: false
Input Size: 440004; match: false
Input Size: 460006; match: true
Input Size: 480007; match: true
Time Duration : 395L
 
Array Size 700000
Test Start
Stream with DFA Table.
match: false, table: [|0; 0; 0; 3; 4|]
match: false, table: [|0; 0; 0; 3; 4|]
...
match: false, table: [|0; 0; 0; 3; 4|]
match: true, [|4; 4; 4; 3; 4|]
match: true, table: [|4; 4; 4; 3; 4|]
Time Duration : 21480L
Test Start
Stream with .Net Regex
Input Size: 7000001; match: false
Input Size: 14000001; match: false
...
Input Size: 154000004; match: false
input to big for StringBuilder!
input to big for StringBuilder!
Time Duration : 106610L

Aber wie schneidet die DFA-Tabelle gegen .Net Regex bei großen Texten. Da ist .Net Regex viel schneller. Zum Glück können wir das Matching parallelisieren.

let inline matchParallel dfaContext (s : seq<int * string>) =
    PSeq.map (fun (i, s) -> i, matchInput dfaContext s) s
    |> Seq.sortBy (fun (i, _) -> i)
    |> Seq.reduce (fun (accIdx, accPair) (idx, resultPair)->
         match (snd accPair),(snd resultPair) with
         | true, _  -> accIdx, accPair
         | _, true  -> idx, resultPair
         | other    ->
             let res = Array.map (fun node -> (fst resultPair).[node]) (fst accPair)
             idx, (res, Set.contains res.[0] dfaContext.accept)) 

let inline testRegex nfa regex size letters  =
    let dfaContext = convert letters nfa |> createDFATable
    let regex = new Regex (regex)
    
    let builder = StringBuilder()
    streamInput size |> Seq.iter (fun (item: string) ->
                builder.Append(item).ToString()|>ignore)
    let input = builder.ToString()
    builder.Clear() |>ignore
    let offs = input.Length / Environment.ProcessorCount

    printfn "Regex - %A;Input Length %A" regex input.Length
    let splitSeq = Seq.map (fun i ->
        i, if i + 1 < Environment.ProcessorCount then 
               input.Substring(i * offs, offs) 
           else 
               input.Substring(i * offs)) [0..Environment.ProcessorCount - 1]   
    test (fun () -> printfn "DFA Table Parallel: match - %A" (matchParallel dfaContext splitSeq) )
    test (fun () -> printfn "DFA Table :  match - %A" (matchInput dfaContext input))
    test (fun () -> printfn ".NET Regex : match - %A" (regex.Match(input).Success))

let run () = 
    let regexList = ["aa|bb";".*\(.*007.*\).*"]
    let letters =[' '..'z']

    regexList |> List.iter (fun regex ->
        let nfa = 
            regex |> RegExParsing.parseRegExp |> RegExCompiling.compile FullMatch
        testRegex nfa regex 200 letters 
        testRegex nfa regex 20000 letters
        testRegex nfa regex 200000 letters)
    Console.ReadLine()|>ignore 

Regex - aa|bb;  Input Length 48007
DFA Table Parallel: match - ([|4; 3; 4; 3; 4|], true)
Time Duration : 48L
++++++++++++++++++++++++++++++++++++
DFA Table :  match - ([|4; 4; 4; 3; 4|], true)
Time Duration : 7L
++++++++++++++++++++++++++++++++++++
.NET Regex : match - true
Time Duration : 4L


Regex - aa|bb;  Input Length 4800007

DFA Table Parallel: match - ([|4; 3; 4; 3; 4|], true)
Time Duration : 111L
++++++++++++++++++++++++++++++++++++
DFA Table :  match - ([|4; 4; 4; 3; 4|], true)
Time Duration : 283L
++++++++++++++++++++++++++++++++++++
.NET Regex : match - true
Time Duration : 256L

Regex - aa|bb;  Input Length 48000007

DFA Table Parallel: match - ([|4; 3; 4; 3; 4|], true)
Time Duration : 1052L
++++++++++++++++++++++++++++++++++++
DFA Table :  match - ([|4; 4; 4; 3; 4|], true)
Time Duration : 2814L
++++++++++++++++++++++++++++++++++++
.NET Regex : match - true
Time Duration : 2555L
-----------------------------------------------
Regex - .*\(.*007.*\).*;  Input Length 48007

DFA Table Parallel: match - ([|8; 8; 8; 8; 8; 8; 8; 8; 8; 8; 10; 11; 8; 13|], true)
Time Duration : 7L
++++++++++++++++++++++++++++++++++++
DFA Table :  match - ([|8; 8; 8; 8; 8; 8; 8; 8; 8; 8; 10; 11; 8; 13|], true)
Time Duration : 8L
++++++++++++++++++++++++++++++++++++
.NET Regex : match - true
Time Duration : 3L

Regex - .*\(.*007.*\).*;  Input Length 4800007

DFA Table Parallel: match - ([|8; 8; 8; 8; 8; 8; 8; 8; 8; 8; 10; 11; 8; 13|], true)
Time Duration : 278L
++++++++++++++++++++++++++++++++++++
DFA Table :  match - ([|8; 8; 8; 8; 8; 8; 8; 8; 8; 8; 10; 11; 8; 13|], true)
Time Duration : 564L
++++++++++++++++++++++++++++++++++++
.NET Regex : match - true
Time Duration : 258L

Regex - .*\(.*007.*\).*;  Input
Post veröffentlichen
Length 48000007 DFA Table Parallel: match - ([|8; 8; 8; 8; 8; 8; 8; 8; 8; 8; 10; 11; 8; 13|], true) Time Duration : 2050L ++++++++++++++++++++++++++++++++++++ DFA Table : match - ([|8; 8; 8; 8; 8; 8; 8; 8; 8; 8; 10; 11; 8; 13|], true) Time Duration : 5443L ++++++++++++++++++++++++++++++++++++ .NET Regex : match - true Time Duration : 2618L -----------------------------------------------

Das gesamte Visual Studio Project kann man hier herunterladen.