# Author:  Chao-Kuei Hung
# License: This program is distributed under the same term as Perl
#          itself (GNU GPL or Artistic License)
# URL:     http://www.cyut.edu.tw/~ckhung/ 

package Heap;

use strict;
use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
use Carp;
require Exporter;
use overload
    '""' => 'stringify',
    'fallback' => undef
;

@ISA = qw(Exporter);
@EXPORT = qw();
@EXPORT_OK = qw();
%EXPORT_TAGS = (all => [@EXPORT_OK]);

$VERSION = '0.1';

sub new {
    my ($class, $data, %opts) = @_;
    $class = ref($class) if ref($class);
    my ($self) = bless {"#data"=>[undef, @$data]}, $class;
    # the following options need only be stored and need no further processing
    my ($k);
    foreach $k (qw(-compare -name -trace)) {
	$self->{$k} = exists $opts{$k} ? delete $opts{$k} : undef;
    }
    my (@unknown) = keys %opts;
    carp "unknown option(s) '@unknown' ignored" if (@unknown);

    if (not defined $self->{-compare}) {
	if (grep { ! /^\s*-?\d+(\.\d+)?\s*$/ } @$data) {
	    $self->{-compare} = sub { $_[0] cmp $_[1] };
	} else {
	    $self->{-compare} = sub { $_[0] <=> $_[1] };
	}
    }

    print "$self\n" if $self->{-trace};
    for ($k=int($self->size()/2); $k>=1; --$k) {
	$self->down($k);
	print "$self\n" if ($self->{-trace} and is_po2($k));
    }
    return $self;
}

sub is_po2 {
    my ($n) = @_;
    my ($po2) = 1;
    while ($po2 <= $n) {
	return 1 if $n == $po2;
	$po2 += $po2;
    }
    return 0;
}

sub size {
    my ($self, $newsize) = @_;
    $#{$self->{"#data"}} = $newsize if (defined $newsize);
    return $#{$self->{"#data"}};
}

sub is_empty {
    my ($self) = @_;
    return $self->size() <= 0;
}

sub stringify {
    my ($self) = @_;
    return "PQ[" . join(" ", @{$self->{'#data'}}[1..$self->size()]) . "]";
}

sub up {
    my ($self, $k) = @_;
    my ($v) = $self->{"#data"}[$k];
    while ($k > 1 && $self->{-compare}->($v,
	$self->{"#data"}[int($k/2)] ) < 0) {
	$self->{"#data"}[$k] = $self->{"#data"}[int($k/2)];
	$k = int($k/2);
    }
    $self->{"#data"}[$k] = $v;
}

sub down {
    my ($self, $k) = @_;
    my ($v) = $self->{"#data"}[$k];
    my ($lighter);
    while ($k*2 <= $self->size()) {
	$lighter = $k * 2;
	++$lighter if ($lighter < $self->size() && $self->{-compare}->(
	    $self->{"#data"}[$lighter+1],
	    $self->{"#data"}[$lighter]
	) < 0);
	last if ($self->{-compare}->($v, $self->{"#data"}[$lighter]) <= 0);
	$self->{"#data"}[$k] = $self->{"#data"}[$lighter];
	$k = $lighter;
    }
    $self->{"#data"}[$k] = $v;
}

sub insert {
    my ($self, $x) = @_;
    push(@{ $self->{"#data"} }, $x);
    $self->up($self->size());
    printf "(%3s) $self\n", $x if $self->{-trace};
}

sub remove {
    my ($self) = @_;
    my ($ret) = $self->{"#data"}[1];
    my ($last) = pop @{ $self->{"#data"} };
    return $ret if $self->size() <= 0;
    $self->{"#data"}[1] = $last;
    $self->down(1);
    printf "%3s | $self\n", $ret if $self->{-trace};
    return $ret;
}

if ($0 =~ /Heap.pm$/) {
# being tested as a stand-alone program, so run test code.

print "O(n) time buildheap (one output after finishing each layer)\n";
my ($data);
# 產生 20 個介於 [0..100) 之間的整數:
# $data = [ map { int(rand(100)) } 1..20 ];
# 或是直接敲入數據:
$data = [ qw(68 45 97 42 67 70 54 23 68 37 57 76 7 46 51 87 83 37 19 29) ];

my ($pq) = Heap->new($data, -trace=>1);

print "\nRandomly insert some elements\n";
$pq->insert(12);
$pq->insert(7);
$pq->insert(25);
$pq->insert(2);

print "\nRemove the smallest element one by one\n";
while ($pq->size() > 0) { $pq->remove(); }

}

1;

