Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
13 / 13
CRAP
100.00% covered (success)
100.00%
172 / 172
Heap
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
13 / 13
40
100.00% covered (success)
100.00%
172 / 172
 __construct($array = null) // [\SplHeap]
100.00% covered (success)
100.00%
1 / 1
5
100.00% covered (success)
100.00%
21 / 21
 extract() // -> mixed [\SplHeap]
100.00% covered (success)
100.00%
1 / 1
4
100.00% covered (success)
100.00%
21 / 21
 extractInsert($value) // -> mixed
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
18 / 18
 insert($value) // [\SplHeap]
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
7 / 7
 recoverFromCorruption() // [\SplHeap]
100.00% covered (success)
100.00%
1 / 1
4
100.00% covered (success)
100.00%
19 / 19
 top() // -> mixed [\SplHeap]
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
7 / 7
 update($value1, $value2) // [\SEIDS\Heaps\AbstractHeap]
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
16 / 16
 parentIndex($index)
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
10 / 10
 childOneIndex($index)
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
6 / 6
 childTwoIndex($index)
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
6 / 6
 swap($a, $b)
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
7 / 7
 siftDown($index)
100.00% covered (success)
100.00%
1 / 1
7
100.00% covered (success)
100.00%
25 / 25
 siftUp($index)
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
9 / 9
<?php namespace SEIDS\Heaps\Binary;
//==============================================================================
// PHP SEIDS: Supplementary, Easily Interchangeable Data Structures
// 
// Copyright 2015, Daniel A.C. Martin
// Distributed under the MIT License.
// (See LICENSE file for details.)
//==============================================================================
use \SEIDS\Heaps\ConstructorException;
use \SEIDS\Heaps\ExtractException;
use \SEIDS\Heaps\PeekException;
use \SEIDS\Heaps\RecoverException;
use \SEIDS\Heaps\UpdateException;
abstract class Heap extends \SEIDS\Heaps\Heap
{
    ////////////////////////////////////////////////////////////////////////////
    // Members
    ////////////////////////////////////////////////////////////////////////////
    
    protected $btree    = array(null); // Array(int => Item)
    protected $hasht    = array();     // Array(mixed => Array(int => int))
    protected $last_tag = 0;           // int
    
    ////////////////////////////////////////////////////////////////////////////
    // Public methods - Implement the same interface as \SplHeap but with the
    //                  ability to update.
    ////////////////////////////////////////////////////////////////////////////
    
    public function __construct($array = null) // [\SplHeap]
    {
        if($array !== null)
        {
            if(is_array($array))
            {
                // Heapify & tag
                $this->btree = $array;
                $first       = true;
                
                array_unshift($this->btree, null);
                
                foreach($this->btree as $i => $e)
                {
                    if($first)
                    {
                        $first = false;
                    }
                    else
                    {
                        $v                   = $this->hashtIndex($e);
                        $t                   = ++$this->last_tag;
                        $this->hasht[$v][$t] = $i;
                        $this->btree[$i]     = new Item($e, $t);
                        
                        $this->siftUp(++$this->size);
                    }
                }
            }
            else
            {
                throw new ConstructorException('Argument to constructor of Heap must be an array, ' . gettype($array) . ' given');
            }
        }
    }
    
    public function extract() // -> mixed [\SplHeap]
    {
        $r = null;
        
        if($this->valid())
        {
            $r = $this->top();
            $v = $this->hashtIndex($r);
            
            unset($this->hasht[$v][$this->btree[1]->tag]);
            
            if(empty($this->hasht[$v]))
            {
                unset($this->hasht[$v]);
            }
            
            $bottom = array_pop($this->btree);
            
            --$this->size;
            
            if($this->size > 0)
            {
                $this->btree[1]                                              = $bottom;
                $this->hasht[$this->hashtIndex($bottom->data)][$bottom->tag] = 1;
                
                $this->siftDown(1);
            }
        }
        else
        {
            throw new ExtractException('Can\'t extract from an empty heap');
        }
        
        return $r;
    }
    
    public function extractInsert($value) // -> mixed
    {
        $r = null;
        
        if($this->valid())
        {
            $r = $this->top();
            $v = $this->hashtIndex($r);
            
            unset($this->hasht[$v][$this->btree[1]->tag]);
            
            if(empty($this->hasht[$v]))
            {
                unset($this->hasht[$v]);
            }
            
            $v                   = $this->hashtIndex($value);
            $t                   = ++$this->last_tag;
            $this->hasht[$v][$t] = 1;
            $this->btree[1]      = new Item($value, $t);
            
            $this->siftDown(1);
        }
        else
        {
            throw new ExtractException('Can\'t extract from an empty heap');
        }
        
        return $r;
    }
    
    public function insert($value) // [\SplHeap]
    {
        ++$this->size;
        
        $v                   = $this->hashtIndex($value);
        $t                   = ++$this->last_tag;
        $this->hasht[$v][$t] = $this->size;
        
        array_push($this->btree, new Item($value, $t));
        $this->siftUp($this->size);
        
        // SplHeap documentation claims that this function returns void but this
        // does not seem to be the case!
        return true;
    }
    
    // Not entirely sure what this function is supposed to do.
    public function recoverFromCorruption() // [\SplHeap]
    {
        if(null !== array_shift($this->btree))
        {
            throw new RecoverException('Unable to recover heap');
        }
        else
        {
            // Re-heapify & re-tag
            $this->size     = 0;
            $this->hasht    = array();
            $this->last_tag = 0;
            $first          = true;
            
            array_unshift($this->btree, null);
            
            foreach($this->btree as $i => $e)
            {
                if($first)
                {
                    $first = false;
                }
                else
                {
                    $v                    = $this->hashtIndex($e->data);
                    $t                    = ++$this->last_tag;
                    $this->hasht[$v][$t]  = $i;
                    $this->btree[$i]->tag = $t;
                    
                    $this->siftUp(++$this->size);
                }
            }
        }
        
        // SplHeap documentation claims that this function returns void but this
        // does not seem to be the case!
        return true;
    }
    
    public function top() // -> mixed [\SplHeap]
    {
        $r = null;
        
        if($this->valid())
        {
            $r = $this->btree[1]->data;
        }
        else
        {
            throw new PeekException('Can\'t peek at an empty heap');
        }
        
        return $r;
    }
    
    // I believe this runs in O(log(n))
    public function update($value1, $value2) // [\SEIDS\Heaps\AbstractHeap]
    {
        $v = $this->hashtIndex($value1);
        
        if(empty($this->hasht[$v]))
        {
            throw new UpdateException('Unable to find element in heap');
        }
        else
        {
            $t = array_keys($this->hasht[$v])[0];
            $i = $this->hasht[$v][$t];
            
            $this->btree[$i] = new Item($value2, $t);
            
            unset($this->hasht[$v][$t]);
            
            if(empty($this->hasht[$v]))
            {
                unset($this->hasht[$v]);
            }
            
            $this->hasht[$this->hashtIndex($value2)][$t] = $i;
            
            $this->siftUp($i);
            $this->siftDown($i);
        }
    }
    
    ////////////////////////////////////////////////////////////////////////////
    // Protected methods
    ////////////////////////////////////////////////////////////////////////////
    
    protected function parentIndex($index)
    {
        $r = false;
        
        if($index > 1)
        {
            $r = floor($index / 2);
        }
        else if($index === 1)
        {
            $r = null;
        }
        
        return $r;
    }
    
    protected function childOneIndex($index)
    {
        $r = 2 * $index;
        
        if($r > $this->size)
        {
            $r = null;
        }
        
        return $r;
    }
    
    protected function childTwoIndex($index)
    {
        $r = 2 * $index + 1;
        
        if($r > $this->size)
        {
            $r = null;
        }
        
        return $r;
    }
    
    protected function swap($a, $b)
    {
        $elem_a = $this->btree[$a];
        $elem_b = $this->btree[$b];
        
        $this->hasht[$this->hashtIndex($elem_a->data)][$elem_a->tag] = $b;
        $this->hasht[$this->hashtIndex($elem_b->data)][$elem_b->tag] = $a;
        
        $this->btree[$a] = $elem_b;
        $this->btree[$b] = $elem_a;
    }
    
    protected function siftDown($index)
    {
        $not_done = true;
        
        while($not_done)
        {
            $largest         = $index;
            $child_one_index = $this->childOneIndex($index);
            $child_two_index = $this->childTwoIndex($index);
            
            if
            (
                   (null !== $child_one_index)
                && (0 < $this->compare($this->btree[$child_one_index]->data, $this->btree[$largest]->data))
            )
            {
                $largest = $child_one_index;
            }
            
            if
            (
                   (null !== $child_two_index)
                && (0 < $this->compare($this->btree[$child_two_index]->data, $this->btree[$largest]->data))
            )
            {
                $largest = $child_two_index;
            }
            
            if($largest !== $index)
            {
                $this->swap($largest, $index);
                
                $index = $largest;
            }
            else
            {
                $not_done = false;
            }
        }
    }
    
    protected function siftUp($index)
    {
        while(0 < ($parent_index = $this->parentIndex($index)) )
        {
            if(0 < $this->compare($this->btree[$index]->data, $this->btree[$parent_index]->data))
            {
                $this->swap($parent_index, $index);
                
                $index = $parent_index;
            }
            else
            {
                $index = 1;
            }
        }
    }
}