Home > Net >  Dafny predicate isBinarySearchTree
Dafny predicate isBinarySearchTree

Time:11-13


I have to write a little BST (binary search tree) class in Dafny.
I begin with Dafny then write a class and an insert method was the easiest part.

I tried multiple time to write a recursive predicate which can check if the tree passed as argument is a BST (without balancing condition, a simple binary tree following the rule left.value < node.value && right.value > node.value).

I found in another StackOverflow post a way to do it passing a function in a predicate and the main recursives check are in the function but it doesn't seem to work.

The error is basically 'A pre-condition for this call might not hold'.
Here is the code:

datatype Tree = Nil | Node(left: Tree, value: int, right: Tree)

class TreeADT{  

    function isBST(tree: Tree): bool
        decreases tree
    {
        match tree
        case Nil => true
        case Node(_,_,_) =>
            (tree.left == Nil || tree.left.value < tree.value) 
            && (tree.right == Nil || tree.right.value > tree.value) 
            && isBST(tree.left) && isBST(tree.right)
    }

    predicate isBinarySearchTree(tree: Tree)
    {
        isBST(tree)
    }
    
    method insert(tree: Tree, value: int) returns (toAdd: Tree) //maybe remove
        requires isBinarySearchTree(tree) 
        decreases tree;
        // ensures isBinarySearchTree(toAdd) //same error appear here
    {
        if(tree == Nil) {return Node(Nil, value, Nil);}
        else{
            if(value == tree.value) {return tree;}
            var temp: Tree;
            if(value < tree.value){
                temp := insert(tree.left, value);
                toAdd := Node(temp, tree.value, tree.right);
            }else{
                temp := insert(tree.right, value);
                toAdd := Node(tree.left, tree.value, temp);
            }
            return toAdd;
        }
            
    }

    method printOrderedTree(tree:Tree)
        decreases tree
    {
        if tree == Nil {}
        else { 
            printOrderedTree(tree.left); 
            print tree.value, ", "; 
            printOrderedTree(tree.right);
        }
    }


    method Main() {
        var t := insert(Nil, 5);
        var u := insert(t, 2); // error on pre-condition here
        print t, "\n";
        print u, "\n";
        printOrderedTree(u);
        var b:bool := isBST(u);
    }
}

I also tried to do it entirely in the predicate but the recursive check seems to doesn't work anyway.

Any idea to get recursion check instead of loop check in a predicate?

Thanks for reading.

Edit:
Following James's answer i modified my code

datatype Tree = Nil | Node(left: Tree, value: int, right: Tree)

predicate isBinarySearchTree(tree: Tree)
    decreases tree
{
    match tree
    case Nil => true
    case Node(_,_,_) =>
        (tree.left == Nil || tree.left.value < tree.value) 
        && (tree.right == Nil || tree.right.value > tree.value) 
        && isBinarySearchTree(tree.left) && isBinarySearchTree(tree.right)
        && treeMin(tree.value, tree.right) && treeMax(tree.value, tree.left)
}

predicate treeMax(max: int, tree: Tree)
    decreases tree
{
    match tree
    case Nil => true
    case Node(left,v,right) => (max > v) && treeMax(max, left) && treeMax(max, right)
}

predicate treeMin(min: int, tree:Tree)
    decreases tree
{
    match tree
    case Nil => true
    case Node(left,v,right) => (min < v) && treeMin(min, left) && treeMin(min, right)
}

method insert(tree: Tree, value: int) returns (toAdd: Tree) //maybe remove
    requires isBinarySearchTree(tree) 
    decreases tree;
    ensures isBinarySearchTree(toAdd)
{
    if(tree == Nil) {return Node(Nil, value, Nil);}
    else{
        if(value == tree.value) {return tree;}
        var temp: Tree;
        if(value < tree.value){
            temp := insert(tree.left, value);
            toAdd := Node(temp, tree.value, tree.right);
        }else{
            temp := insert(tree.right, value);
            toAdd := Node(tree.left, tree.value, temp);
        }
        return toAdd;
    }
        
}

method printOrderedTree(tree:Tree)
    decreases tree
{
    if tree == Nil {}
    else { 
        printOrderedTree(tree.left); 
        print tree.value, ", "; 
        printOrderedTree(tree.right);
    }
}


method Main() {
    var t := insert(Nil, 5);
    var u := insert(t, 2);
    print t, "\n";
    print u, "\n";
    u := insert(u, 1);
    u := insert(u, 3);
    u := insert(u, 7);
    u := insert(u, 6);
    u := insert(u, 4);
    printOrderedTree(u);
}

But same problem occurs in requires and ensures statement, I now check if all value at left side are lesser and all at right side are greater but this error occurs again

A postcondition might not hold on this return path.

And if I comment out the ensures statement I get the following error:

A precondition for this call might not hold.

All constructives ideas and dafny tips will be read with attention.

Thanks.

CodePudding user response:

There are several issues with your code.

(1) What is the purpose of the TreeADT class? In Dafny, classes are usually used to represent mutable objects, but your class has no fields or mutator methods, and you use a datatype to hold the data, so you can just get rid of the class altogether.

(2) Your definition of isBST is wrong. Here is an example:

method UhOh()
  ensures isBST(Node(Node(Nil, 3, Node(Nil, 7, Nil)), 5, Nil))
{}

This tree is not a binary search tree because 7 is greater than 5 but 7 is in the left subtree of 5. But your definition allows this tree.

(3) The concrete problem you are running into is that Dafny cannot prove that the variable t in Main is a binary search tree. I see you have that postcondition of insert commented out. Why? You will need that postcondition.


I'm also not sure what you meant by "passing a function in a predicate". You have a useless (though harmless) wrapper predicate isBST. In Dafny, the word predicate is nothing but an abbreviation for a function whose return type is bool.

CodePudding user response:

After adding few assert statements you can see what dafny is unable to verify.

   if (value < tree.value) {
        temp := insert(tree.left, value);
        toAdd := Node(temp, tree.value, tree.right);
        assert treeMax(tree.value, temp);
    }
    else {
        temp := insert(tree.right, value);
        toAdd := Node(tree.left, tree.value, temp);
        assert treeMin(tree.value, temp);
    }

Dafny is unable to verify added assert holds. Way to think about why dafny is unable to verify is it looks abstractly all method with given pre and post conditions forgetting implementation. insert method precondition is input is valid binary search tree and postcondition is output is valid binary tree. So insert method which always return below tree for example is valid implementation.

enter image description here

Now it is not hard to see why treeMax or treeMin will not hold when temp is always Tree(Tree(Nil, 1, Nil), 3, Tree(Nil, 5, Nil)).

Looking back bigger issue is there is no link between input tree and output tree provided by ensures.

  • Related