Canadian Computing Competition: 2004 Stage 2, Day 1, Problem 3
You may be wondering what is the simplest computer we can create that can still perform useful computation. Over the years, theoretical computer scientists have devised various simple models of computation such as Turing machines and lambda calculus. In this problem, we will see how we can perform arbitrary computation in an even simpler system devised in 1924 by Moses Schönfinkel, using just the letters and . We will arrange these letters in a binary tree to provide some structure. Specifically, we will manipulate binary trees in which every leaf node is either an or . An example of such a tree is shown in Figure 1(a).
We can encode such a binary tree as a string in the following way. To represent a leaf node, we write either or . To represent a non-leaf node, we write , replacing with the string representation of the left child and with the string representation of the right child. For example, the tree in Figure 1(a) would be represented by the string . The binary trees are transformed according to the following rules, which will attempt to apply in the order given. That is, we will try to apply rule (1) first, and if we can't, we will try to apply rule (2) next, and so on. Once we have applied a rule, we would begin checking at rule (1) on the root of the tree.
If the tree has the form of Figure 1(b), where and are arbitrary subtrees, we replace it with only the subtree . In string form, if the string representation has the form , where and are the string representations of some arbitrary subtrees, we replace it with just .
If the tree has the form of Figure 1(c), where , , and are arbitrary subtrees, we replace it with the tree shown in Figure 1(d), which contains one copy each of and , and two copies of . In string form, we are replacing a string of the form with .
If we cannot find any transformation to perform using any of the preceding rules, we recursively apply these transformation rules to the subtree rooted at the left child of the root.
If we cannot find any transformation to perform using any of the preceding rules, we recursively apply these transformation rules to the subtree rooted at the right child of the root.
If we cannot find any transformation to perform using any of the preceding rules, we print out the string representation of the resulting tree and stop.
How do we use these simple rules to perform computations? We can start by defining a representation of natural numbers. Many representations are possible, but the following is the most popular, devised by Alonzo Church. We will define zero to be , and a successor operator . We can then define the natural numbers as , , , and so on, always replacing each symbol such as and by the subtree we defined for it. So the number will be represented by the tree
Notice that this has four occurrences of the subtree , followed by the subtree . Numbers encoded in this way can be added using the subtree
For example, if we construct the tree , again replacing , , and by the appropriate subtrees, and apply the transformation rules, we will eventually end up with the tree . Multiplication can be performed using the simpler subtree . However, the results produced using some operators such as may not look like the numbers we just defined, although they are equivalent in that they behave the same way. We can use a normalization operator
to make them look the same if they are equivalent. So, applying the transformation rules to the tree produces the tree .
With a bit more playing around, we can come up with trees for other operations one might expect in a programming language, such as comparisons, conditionals, and recursion. These operators can be used to write more complicated programs. For example, the following tree computes factorials:
If we combine it with the normalization operator and the number , for example, and apply the transformation rules to the tree , we end up with the tree representing (which is ).
Input Specification
The input will consist of several strings representing trees, one tree on each line. No input line will contain more than characters. The last line of input will be blank.
Output Specification
For each line of input except the last blank line, your program must repeatedly apply the transformation rules to the given tree, until no further transformations are possible (rule 5). It must then print out, on a single line, the string representation of the resulting tree. Although for some trees, such as
it is possible to continue applying the rules forever, we will not include any such trees in the test data.
Sample Input
((KS)K)
((KK)S)
(((SK)S)K)
(((SS)K)S)
(((SK)K)K)
((((S((SK)K))(K(S((S(KS))K))))((S((S(KS))K))(K((SK)K))))((S((S(KS))K))(K((SK)K))))
(((S((S((SK)K))(K(S((S(KS))K)))))(K(K((SK)K))))(((S((S(KS))K))((SK)K))((S((S(KS))K))((SK)K))))
Sample Output
S
K
K
((SS)(KS))
K
((S((S(KS))K))((S((S(KS))K))(K((SK)K))))
((S((S(KS))K))((S((S(KS))K))((S((S(KS))K))((S((S(KS))K))(K((SK)K))))))
Comments