Binary-Heap-Search

 view release on metacpan or  search on metacpan

lib/Binary/Heap/Search.pm  view on Meta::CPAN

use Carp;
use Data::Dump qw(dump);
our $VERSION = 2017.117;

if (0)                                                                          # Save to S3:- this will not work, unless you're me, or you happen, to know the key
 {my $z = 'BinaryHeapSearch.zip';
  print for qx(zip $z $0 && aws s3 cp $z s3://AppaAppsSourceVersions/$z && rm $z);
 }

#1 Methods
sub new($)                                                                      # Create a new Binary Search-able Heap
 {my ($compare) = @_;                                                           # Sub to perform <=> on two elements of the heap
  return bless {compare=>$compare};
 }

sub arrays    {$_[0]{arrays} //= []}                                            ## Each array in the heap is in the order created by compare
sub compare   {$_[0]{compare}}                                                  ## A sub that performs <=>/cmp on any two elements on the heap
sub size      {scalar @{$_[0]->heaps}}                                          ## Number of arrays in the heap

sub mergeArrays($$$)                                                            ## Merge two ordered arrays to make a new ordered array
 {my ($compare, $b, $c) = @_;                                                   # Sub to order elements, first array of elements to be merged, second array of elements to be merged
  my @a;
  while(@$b and @$c)                                                            # Sequentially merge the two arrays
   {my $k = $compare->($$b[0], $$c[0]);                                         # Compare the smallest elements in each array
    if    ($k < 0) {push @a, shift @$b}                                         # Save smallest element
    elsif ($k > 0) {push @a, shift @$c}
    else {confess "Duplicate entry ", dump($$b[0])}
   }
  @a, @$b, @$c                                                                  # Add remaining un-merged elements, the order does not matter because one of the arrays will be emptied by the preceding merge
 }

sub mergeAdjacentArrays($$$)                                                    ## Merge adjacent arrays
 {my ($arrays, $compare, $start) = @_;                                          # Index of first array to be merged

  for my $small(reverse 1..$start)                                              # Each array that might be merge-able
   {my $b = $arrays->[$small-1];                                                # Larger array
    my $c = $arrays->[$small-0];                                                # Smaller array
    if ($b and @$b and $c and @$c and @$b <= @$c * 2)                           # Adjacent arrays are close enough in size to warrant merging
     {$arrays->[$small-1] = [mergeArrays($compare, $b, $c)];
     }
    else                                                                        # Adjacent arrays are to different in size to be worth merging
     {splice @$arrays, $small+1, $start-$small if $small != $start;             # Remove previously merged arrays - this inefficient operation is done just once on a small array
      return
     }
   }
  $#$arrays = 0;                                                                # All the arrays have been merged into just one array
 }

sub add($$)                                                                     # Add an element to the heap of ordered arrays
 {my ($heap, $element) = @_;                                                    # Heap, element (that can be ordered by compare)
  my $compare = $heap->compare;
  my $arrays  = $heap->arrays;

  for my $arrayIndex(0..$#$arrays)                                              # Try to put the element on top of one of the existing arrays starting at the largest one.  We could of course just add the new element as a single array at the end and t...
   {my $array = $arrays->[$arrayIndex];
    my $c = $compare->($element, $array->[-1]);                                 # Compare the element to be added to the topmost element of the current array
    if ($c == 1)                                                                # The element to be added is greater than the largest element in the current array
     {push @$array, $element;                                                   # Add the element to the top of this array
      mergeAdjacentArrays($arrays, $compare, $arrayIndex) if $arrayIndex;       # Merge two adjacent arrays if they are close enough in size
      return;
     }
    elsif ($c == 0)                                                             # Duplicate element detected
     {confess "Duplicate element ", dump($element);
     }
   }
  push @$arrays, [$element];                                                    # Cannot put element on top of any array in the heap so create a new array
  mergeAdjacentArrays($arrays, $compare, $#$arrays) if $#$arrays;               # Try to merge the newest array if there is an existing array into which to merge it
 }

sub binarySearch($$$)                                                           ## Find an element in an array using binary search
 {my ($array, $compare, $element) = @_;                                         # Array, element
  my $m = 0;                                                                    # Check the lower bound of the array
  my $e = $array->[$m];                                                         # Lowest element in the array
  my $c = $compare->($element, $e);                                             # Compare with lowest element in the array
  return $e if $c == 0;                                                         # Equal to the lowest element
  return undef unless $c == 1;                                                  # Lower than any element in the array
  my $M = $#$array;                                                             # Check the upper bound of the array
  my $E = $array->[$M];                                                         # Highest element in the array
  my $C = $compare->($element, $E);                                             # Compare with highest element in the array
  return $E    if $C == 0;                                                      # Equal to the highest element

lib/Binary/Heap/Search.pm  view on Meta::CPAN

  while($m+1 < $M)                                                              # Narrow the zone
   {my $i = int(($m+$M)/2);                                                     # Index of a point halfway between
    my $e = $array->[$i];                                                       # Element at mid point
    my $c = $compare->($element, $e);                                           # Compare
    return $e if $c == 0;                                                       # Found
    ($c == 1 ? $m : $M) = $i;
   }                                                                            # Continue to narrow the range
  undef                                                                         # Not found
 }

sub find($$)                                                                    # Find an element in the heap
 {my ($heap, $element) = @_;                                                    # Heap, element (that can be ordered by compare)
  my $compare = $heap->compare;
  my $arrays  = $heap->arrays;

  for my $array(@$arrays)                                                       # Use a binary search on each array in the heap
   {my $e = binarySearch($array, $compare, $element);
    return $e if defined $e                                                     # Return matching element
   }
  undef                                                                         # Element not found
 }

lib/Binary/Heap/Search.pm  view on Meta::CPAN

__DATA__
use utf8;
use Test::More tests=>85;

my $compare = sub
 {my ($a, $b) = @_;
  defined($a) && defined($b) or confess;
  $a cmp $b
 };

sub newHeap($$)
 {my ($string, $result) = @_;
  my $h = Binary::Heap::Search::new($compare);
  $h->add($_) for split //, $string;
  my $dump = dumpHeap($h);
# say STDERR "newHeap(\'$string\', \'$dump\');";
  ok $dump eq $result;
  $h
 }

sub dumpHeap($)
 {my ($h) = @_;
  join ',', map {join '', @$_} @{$h->arrays}
 }

newHeap('0',  '0') ;                                                            # Ascending
newHeap('01', '01');
newHeap('012', '012');
newHeap('0123', '0123');
newHeap('01234', '01234');
newHeap('012345', '012345');



( run in 0.574 second using v1.01-cache-2.11-cpan-65fba6d93b7 )