[Haskell-cafe] Construct all possible trees

Andrew Coppin andrewcoppin at btinternet.com
Thu Jun 14 13:32:58 EDT 2007


Well, I eventually came up with this:

---------------------------------

data Tree = Leaf Int | Branch Tree Tree deriving Show

pick :: [x] -> [(x,[x])]
pick = pick_from []

pick_from :: [x] -> [x] -> [(x,[x])]
pick_from ks [] = []
pick_from ks xs = (head xs, ks ++ tail xs) : pick_from (ks ++ [head xs]) 
(tail xs)

trees :: [Int] -> [Tree]
trees = map fst . (\ts -> all_trees 1 (2 * length ts) ts) . map Leaf

all_trees :: Int -> Int -> [Tree] -> [(Tree,[Tree])]
all_trees n m ts
  | n > m     = []
  | otherwise = pick ts ++ sub_trees n m ts

sub_trees :: Int -> Int -> [Tree] -> [(Tree,[Tree])]
sub_trees n m ts = do
  let n2 = n * 2
  (t0,ts0) <- all_trees n2 m ts
  (t1,ts1) <- all_trees n2 m ts0
  return (Branch t0 t1, ts1)

-----------------------------

For example, trees [1,2,3] now gives

Leaf 1
Leaf 2
Leaf 3
Branch (Leaf 1) (Leaf 2)
Branch (Leaf 1) (Leaf 3)
Branch (Leaf 1) (Branch (Leaf 2) (Leaf 3))
Branch (Leaf 1) (Branch (Leaf 3) (Leaf 2))
Branch (Leaf 2) (Leaf 1)
Branch (Leaf 2) (Leaf 3)
Branch (Leaf 2) (Branch (Leaf 1) (Leaf 3))
Branch (Leaf 2) (Branch (Leaf 3) (Leaf 1))
Branch (Leaf 3) (Leaf 1)
Branch (Leaf 3) (Leaf 2)
Branch (Leaf 3) (Branch (Leaf 1) (Leaf 2))
Branch (Leaf 3) (Branch (Leaf 2) (Leaf 1))
Branch (Branch (Leaf 1) (Leaf 2)) (Leaf 3)
Branch (Branch (Leaf 1) (Leaf 3)) (Leaf 2)
Branch (Branch (Leaf 2) (Leaf 1)) (Leaf 3)
Branch (Branch (Leaf 2) (Leaf 3)) (Leaf 1)
Branch (Branch (Leaf 3) (Leaf 1)) (Leaf 2)
Branch (Branch (Leaf 3) (Leaf 2)) (Leaf 1)

which looks pretty comprehensive to me!


The derivation wasn't easy. It goes something like this:

First, the "pick" function takes a list and picks a single element from 
it, returning the element picked and the remaining unpicked elements. It 
does this inside the list monad, thus representing every possibel 
choice. (It's defined in terms of pick_from, which isn't used anywhere 
else. The algorithm should be fairly self-evident.)

Next, we have "trees" which transforms a list of integers into a list of 
trivial 1-leaf trees to be processed by "all_trees". The "all_trees" 
function calls pick to select all possible trivial trees, and then calls 
"sub_trees" to pick all possible nontrivial trees.

The code for sub_trees would go something like this:

  sub_trees ts = do
    t0 <- ts
    t1 <- ts
    return (Branch t0 t1)

But now t0 == t1 sometimes, which we cannot allow. Hence the "pick" 
function:

  sub_trees ts = do
    (t0,ts0) <- pick ts
    (t1,ts1) <- pick ts0
    return (Branch t0 t1, ts1)

And now the problem is solved.

However, this only generates all possible 2-leaf trees. To make *all* 
possible trees, we must be recursive:

  sub_trees ts = do
    (t0,ts0) <- all_trees ts
    (t1,ts1) <- all_trees ts0
    return (Branch t0 t1, ts1)

And now it works properly.

Er... wait. Now we have an infinite recursive loop! all_trees --> 
sub_trees --> all_trees (with the same arguments)!

The only way I could figure out to avoid that is to count how big the 
input list is - and hence how deep the tree can possibly be. Then you 
keep track of how deep you are, and abort when you get too deep. I added 
lots of hackery to avoid recomputing stuff. Makes the code look very 
messy and ugly...



More information about the Haskell-Cafe mailing list