0

I have this code:

datatype 'a Tree = Empty | LEAF of 'a | NODE of ('a Tree) list;
val iL1a = LEAF 1;
val iL1b = LEAF 2;
val iL1c = LEAF 3;
val iL2a = NODE [iL1a, iL1b, iL1c];
val iL2b = NODE [iL1b, iL1c, iL1a];
val iL3 = NODE [iL2a, iL2b, iL1a, iL1b];
val iL4 = NODE [iL1c, iL1b, iL3];
val iL5 = NODE [iL4];

fun treeToString f Node = let
    fun treeFun (Empty) = ["(:"]
    | treeFun (NODE([])) = [")"]
    | treeFun (LEAF(v)) = [f v]
    | treeFun (NODE(h::t)) = [""] @ ( treeFun (h)) @ ( treeFun (NODE(t)) )
    in
    String.concat(treeFun Node)
end;

treeToString Int.toString iL5;

When I run my function I get the output: "32123)231)12)))".

The answer should be "((32((123)(231)12)))".

I've tried modifying my function to add ( in every place I can think but I cannot figure out where I should be adding "(". Where have I messed up?

Edit: I believe I need to use map or List.filter somewhere, but am not sure where.

user494948
  • 51
  • 3
  • 9

1 Answers1

2

It looks like your method of recursion over the tail of a list node is the problem. Instead of treeFun h appended to treefun (NODE(t)), try using this for the NODE case:

 treeFun (NODE(items)) = ["("] @ List.concat (map treeFun items) @ [")"]

That is, map treeFun over the entire contents of the node, and surround the results with "(" and ")". That definition might be a bit too terse for you to understand what's going on, so here's a more verbose form that you might find clearer:

| treeFun (NODE(items)) =
  let val subtree_strings : string list list = map treeFun items
      val concatenated_subtrees : string list = List.concat subtree_strings
      in ["("] @ concatenated_subtrees @ [")"]
      end

subtree_strings is the result of taking all the subtrees in the given node, and turning each of them to a list of strings by recursively calling treeFun on each subtree. Since treeFun gives back a list of strings each time it's called, and we're calling it on an entire list of subtrees, the result is a corresponding list of lists of subtrees. So for instance, if we called map treeFun [LEAF 1, LEAF 2, LEAF 3], we'd get back [["1"], ["2"], ["3"]].

That's not the answer we want, since it's a list of lists of strings rather than a list of plain strings. We can fix that using List.concat, which takes a list of lists, and forms a single list of all the underlying items. So for instance List.concat [["1"], ["2"], ["3"]] returns ["1", "2", "3"]. Now all we have to do is put the parentheses around the result, and we're done.

Notice that this strategy works just as well for completely empty nodes as it does for nodes with one or more subtrees, so it eliminates the need for the second case of treeFun in your original definition. Generally, in ML, it's a code smell if a function of one argument doesn't have exactly one case for each constructor of the argument's type.

jacobm
  • 13,790
  • 1
  • 25
  • 27
  • Thank you for the advice. I realize I need to map things but the problem is I do not really know how to map it correctly in SML. I've been trying for about 4 hours now but there are no real tutorials for my situation. I've tried "| treeFun (NODE(h::t)) = ["("] @ (map (fn y => y = treeFun (h) ) [(treeFun (NODE(t)) )] ) @ [")"]" things like that but get errors and have tried many other methods but am stuck. Can you perhaps dumb down what I need to do? I'm going to keep working on it but any further advice would help me a lot. – user494948 Nov 08 '10 at 00:28
  • Thank you for the long explanation, I was not aware you could use the map function like that. That helps me a lot especially for another function I am creating. – user494948 Nov 08 '10 at 06:22