views:

313

answers:

2

Say I have the following Haskell tree type, where "State" is a simple wrapper:

data Tree a = Branch (State a) [Tree a]
            | Leaf   (State a)
            deriving (Eq, Show)

I also have a function "expand :: Tree a -> Tree a" which takes a leaf node, and expands it into a branch, or takes a branch and returns it unaltered. This tree type represents an N-ary search-tree.

Searching depth-first is a waste, as the search-space is obviously infinite, as I can easily keep on expanding the search-space with the use of expand on all the tree's leaf nodes, and the chances of accidentally missing the goal-state is huge... thus the only solution is a breadth-first search, implemented pretty decent over here, which will find the solution if it's there.

What I want to generate, though, is the tree traversed up to finding the solution. This is a problem because I only know how to do this depth-first, which could be done by simply called the "expand" function again and again upon the first child node... until a goal-state is found. (This would really not generate anything other then a really uncomfortable list.)

Could anyone give me any hints on how to do this (or an entire algorithm), or a verdict on whether or not it's possible with a decent complexity? (Or any sources on this, because I found rather few.)

+3  A: 

I'm curious why you need the expand function at all--why not simply construct the entire tree recursively and perform whatever search you want?

If you're using expand in order to track which nodes are examined by the search, building a list as you go seems simpler, or even a second tree structure.

Here's a quick example that just returns the first result it finds, with the spurious Leaf constructor removed:

data State a = State { getState :: a } deriving (Eq, Show)

data Tree a = Branch { 
    state :: State a, 
    children :: [Tree a]
    } deriving (Eq, Show)

breadth ts = map (getState . state) ts ++ breadth (concatMap children ts)
search f t = head $ filter f (breadth [t])

mkTree n = Branch (State n) (map mkTree [n, 2*n .. n*n])

testTree = mkTree 2

Trying it out in GHCi:

> search (== 24) testTree
24

For contrast, here's a naive depth-first search:

depth (Branch (State x) ts) = x : (concatMap depth ts)
dSearch f t = head $ filter f (depth t)

...which of course fails to terminate when searching with (== 24), because the left-most branches are an endless series of 2s.

camccann
Just to spell it out: The "trick" to doing breadth-first search functionally is to iterate over a list of trees, not a single tree. It might be useful to add type annotations to the top level functions breadth and search here.
MtnViewMark
I understand how this level-oriented solution works for BF traversal, but what Dennetik wants is the examined tree up to the point that the solution is found.This seems roughly equivalent to BF numbering, and it's my understanding that level-oriented approaches aren't easy to extend to numbering, while Okasaki's queue-based approach is. That's why I used `unfoldTreeM_BF` in my answer.Am I missing something? Can you expand on how to recover the examined tree with your approach?
Travis Brown
I was using the "expand" function because I have been working with Java for way too long now, and forgot that I could count on lazy evaluation for working on infinite trees. Thanks for reminding me -- as this is what I gather your code does? (Or I'm being plain silly.)
Pepijn
@Travis Brown: No, you're correct--extending my simplistic traversal to extract the examined tree would complicate it substantially, likely involving a state parameter threaded through each call, i.e., the State monad or something like it. Far be it from me to argue with Okasaki!
camccann
@Dennetik: Yep, my code defines an infinite lazy tree, then traverses it. Only nodes that are visited get evaluated. This is Haskell, might as well make laziness work for you! Try playing with it if you like; though it seems that Travis Brown's answer does what you actually needed.
camccann
+4  A: 

Have you looked at Chris Okasaki's "Breadth-First Numbering: Lessons from a Small Exercise in Algorithm Design"? The Data.Tree module includes a monadic tree builder named unfoldTreeM_BF that uses an algorithm adapted from that paper.

Here's an example that I think corresponds to what you're doing:

Suppose I want to search an infinite binary tree of strings where all the left children are the parent string plus "a", and the right children are the parent plus "bb". I could use unfoldTreeM_BF to search the tree breadth-first and return the searched tree up to the solution:

import Control.Monad.State
import Data.Tree

children :: String -> [String]
children x = [x ++ "a", x ++ "bb"]

expand query x = do
  found <- get
  if found
    then return (x, [])
    else do
      let (before, after) = break (==query) $ children x
      if null after
        then return (x, before)
        else do
          put True
          return (x, before ++ [head after])

searchBF query = (evalState $ unfoldTreeM_BF (expand query) []) False

printSearchBF = drawTree . searchBF

This isn't terribly pretty, but it works. If I search for "aabb" I get exactly what I want:

|
+- a
|  |
|  +- aa
|  |  |
|  |  +- aaa
|  |  |
|  |  `- aabb
|  |
|  `- abb
|
`- bb
   |
   +- bba
   |
   `- bbbb

If this is the kind of thing you're describing, it shouldn't be hard to adapt for your tree type.

UPDATE: Here's a do-free version of expand, in case you're into this kind of thing:

expand q x = liftM ((,) x) $ get >>= expandChildren
  where
    checkChildren (before, [])  = return before
    checkChildren (before, t:_) = put True >> return (before ++ [t])

    expandChildren True  = return []
    expandChildren _     = checkChildren $ break (==q) $ children x

(Thanks to camccann for prodding me away from old control structure habits. I hope this version is more acceptable.)

Travis Brown
Dear god, that code, it... I... but... what are you *doing* to that poor State monad? You monster!
camccann
Yeah, it's ugly, but it was off the top of my head. How would you do it? I'll admit that I'm a beginner, but I don't see a way to tell unfoldTreeM_BF to stop expanding children without State.
Travis Brown
Okay I fixed the silly thing I was doing with the query in State, but I still think I need it for knowing when to stop. Isn't this exactly why the tree builder is monadic in the first place?
Travis Brown
Yeah, I think that's pretty much how `unfoldTreeM_BF` is designed to work--it's more that this is a case where using `State` is actually *less* readable than explicit state parameters would be, to my eye. I'd probably trim out some of the superfluous `do` cruft and move things around a bit, but without fundamentally changing the algorithm that'd be about all.
camccann
Thank you, this was exactly what I was looking for! For the record, yes, what I wanted was "the examined tree up to the point that the solution is found" -- the reason being that I implemented breadth-first, best-first and A*-search, and wanted a simple method for examining the paths the algorithms examine. I was also, and still am, slightly frustrated at not being able to come up with this myself.
Pepijn
Haha, that's creepy! I tried rewriting your code a bit last night, then got too tired to finish and post it... but your revised version is very, very similar to what I was going for, particularly the `((,) x) $ get >>=` construct. One minor note, though: `liftM` is, for most purposes, identical to the more common `fmap`, or the often more elegant `<$>` combinator from `Control.Applicative`.
camccann
Speaking of creepy, I actually had `<$>` at first and then realized that I'd need to note the extra `import`.
Travis Brown