CS195Y Lecture 13

2/24/2017

Announcements

Today, we're going to start modeling data structures and getting into more complicated computer science. We'll be looking at binary search trees.

What are some examples/properties of binary search trees?

We've sort of skimmed over the fact that Alloy has a module system. I'm going to take advantage of it today to separate the code over a couple files. There'll be one that defines binary trees and one that looks at descending binary trees.

How might I say what a binary tree is in Alloy? For now, let's only worry about models that have a single tree.

``` module btrees_inclass

sig Node { num: Int, left: Node, right: Node } ```

Q: Shouldn't you say one Node so that they won't be sets of Nodes?

Tim: Defaults! Defaults always trip me up. Since it's a one-column field, Alloy defaults to one.

Q: Shouldn't it be lone Node?

Tim: What happens if we don't make it lone? Once we rule out cycles, then the tree has to go on forever because every node has to have a left and right child.

So, we could fix this with lone, but there's another way. Think about how you'd represent this with algebraic data types. It'll turn out to be more convenient having a concrete notion of a leaf node.

``` module btrees_inclass

abstract sig Node {} sig Empty extends Node {} sig Data extends Node { num: Int, left: Node, right: Node } ```

Q: Do we want to move the notion of nodes having a value into the Node field?

Tim: If we do that, then empty nodes also have to also have a value. Empty nodes are sort of a special node that indicates the absence of something at that child, kind of like null in Java.

Like in your homework, we'r going to define a predicate that checks if we have a binary tree.

``` pred isBTree { -- root reaches everything some r: Node | Node in r.^(left+right) + r

all n: Node | {
    -- not on a cycle
    n not in n.^(left+right)

    -- at most one parent
    lone n.~(left+right)
}

}

run isBTree for 4 ```

Q: Why is it some r instead of one r?

Tim: The constraints will turn out to force there to only be one root. ones are somewhat more expensive for Alloy, so I'm using the least restrictive constraint I can.

Q: Why isn't it lone r? What about the empty tree?

Tim: This predicate won't accept empty trees. We're making a conscious decision to exclude them.

Q: For at most one parent, could you also say lone (left+right).n

Tim: Yes, I think so.

Q: Could you talk about what using ~ does here?

Tim: So, left and right are binary relations from parents to children. Now, if I flip those around, I'll have the child-parent relation, so if I join with n on the left, I'll have all the parents of n.

When we run this, notice that we can have an instance with just an Empty node, so we haven't quite ruled out the empty-tree case. There's also something weird happening with some of our instances. We're getting nodes with the left and right children being the same!

``` pred isBTree { ... all n: Node | { ...

    -- left different from right
    n.left != n.right
}

} ```

Adding this makes the spec unsatisfiable. Why?

Student: When we have an Empty node, n.left is an empty set since left and n.right is also an empty set, and those are equal.

Let's see if Alloy can give us a good enough unsat core. When we adjust the granularity settings, it highlights that nodes are only allowed one left and one right child, that there can't be cycles, and this constraint that we just added. That's pretty good, since if we allow cycles then we can just put Data nodes everywhere and not worry about Empty.

This version is satisfiable:

``` pred isBTree { ... all n: Node | { ...

    -- left different from right
    -- could we move this out to no left & right? Yes!
    no n.left & n.right
}

} ```

In the Empty node case, there's no intersection, so it's still ok.

Now we're going to talk about what I've been deferring for the past half hour and deal with the numbers.

``` pred isOrdered { all n : Node, n': n.left.*(left+right) | { n.num > n'.num }

all n : Node, n': n.right.*(left+right) | {
    n.num < n'.num
}

}

pred isBSTree { isBTree isOrdered }

run isBSTree for 4 ```

This doesn't work, but we're out of time! Here's a hint: isBTree is fine, the problem's with isOrdered.

Q: Don't Empty nodes not have nums? Tim: Exactly! We can show this in the evaluator also. Trying to run our all constraints there gets some strange results.

Adding - Empty so that n' is never an empty node fixes it.

``` pred isOrdered { all n : Node, n': n.left.*(left+right) - Empty | { n.num > n'.num }

all n : Node, n': n.right.*(left+right) - Empty | {
    n.num < n'.num
}

} ```