import org.scalacheck.Gen import org.scalacheck.Prop.forAll import org.scalacheck.Properties package scala.collection.mutable { /** * Property of an AVL Tree : Any node of the tree has a balance value beetween in [-1; 1] */ abstract class AVLTreeTest(name: String) extends Properties(name) { def `2^`(n: Int) = (1 to n).fold(1)((a, b) => b*2) def capacityMax(depth: Int): Int = `2^`(depth+1) - 1 def minDepthForCapacity(x: Int): Int = { var depth = 0 while(capacityMax(depth) < x) depth += 1 depth } def numberOfElementsInLeftSubTree(n: Int): collection.immutable.IndexedSeq[Int] = { val mid = n/2 + n%2 ((1 until mid) .filter { i => math.abs(minDepthForCapacity(i) - minDepthForCapacity(n-i)) < 2 } .flatMap { i => Seq(i, n-(i+1)) }).toIndexedSeq.distinct } def makeAllBalancedTree[A](elements: List[A]): List[AVLTree[A]] = elements match { case Nil => Leaf::Nil case first::Nil => Node(first, Leaf, Leaf)::Nil case first::second::Nil => Node(second, Node(first, Leaf, Leaf), Leaf)::Node(first, Leaf, Node(second, Leaf, Leaf))::Nil case first::second::third::Nil => Node(second, Node(first, Leaf, Leaf), Node(third, Leaf, Leaf))::Nil case _ => { val combinations = for { left <- numberOfElementsInLeftSubTree(elements.size) root = elements(left) right = elements.size - (left + 1) } yield (root, left, right) (combinations.flatMap(triple => for { l <- makeAllBalancedTree(elements.take(triple._2)) r <- makeAllBalancedTree(elements.takeRight(triple._3)) } yield Node(triple._1, l, r))).toList } } def genInput: org.scalacheck.Gen[(Int, List[AVLTree[Int]])] = for { size <- org.scalacheck.Gen.choose(20, 25) elements <- org.scalacheck.Gen.listOfN(size, org.scalacheck.Gen.choose(0, 1000)) selected <- org.scalacheck.Gen.choose(0, 1000) } yield { // selected mustn't be in elements already val list = makeAllBalancedTree(elements.sorted.distinct.map(_*2)) (selected*2+1, list) } def genInputDelete: org.scalacheck.Gen[(Int, List[AVLTree[Int]])] = for { size <- org.scalacheck.Gen.choose(20, 25) elements <- org.scalacheck.Gen.listOfN(size, org.scalacheck.Gen.choose(0, 1000)) e = elements.sorted.distinct selected <- org.scalacheck.Gen.choose(0, e.size-1) } yield { // selected must be in elements already val list = makeAllBalancedTree(e) (e(selected), list) } } trait AVLInvariants { self: AVLTreeTest => def isBalanced[A](t: AVLTree[A]): Boolean = t match { case node: Node[A] => math.abs(node.balance) < 2 && (List(node.left, node.right) forall isBalanced) case Leaf => true } def setup(invariant: AVLTree[Int] => Boolean) = forAll(genInput) { case (selected: Int, trees: List[AVLTree[Int]]) => trees.map(tree => invariant(tree)).fold(true)((a, b) => a && b) } property("Every tree is initially balanced.") = setup(isBalanced) } object TestInsert extends AVLTreeTest("Insert") with AVLInvariants { import math.Ordering.Int property("`insert` creates a new tree containing the given element. The tree remains balanced.") = forAll(genInput) { case (selected: Int, trees: List[AVLTree[Int]]) => trees.map(tree => { val modifiedTree = tree.insert(selected, Int) modifiedTree.contains(selected, Int) && isBalanced(modifiedTree) }).fold(true)((a, b) => a && b) } } object TestRemove extends AVLTreeTest("Remove") with AVLInvariants { import math.Ordering.Int property("`remove` creates a new tree without the given element. The tree remains balanced.") = forAll(genInputDelete) { case (selected: Int, trees: List[AVLTree[Int]]) => trees.map(tree => { val modifiedTree = tree.remove(selected, Int) tree.contains(selected, Int) && !modifiedTree.contains(selected, Int) && isBalanced(modifiedTree) }).fold(true)((a, b) => a && b) } } } object Test extends Properties("AVL") { include(scala.collection.mutable.TestInsert) include(scala.collection.mutable.TestRemove) }