File Coverage

blib/lib/Binary/Heap/Search.pm
Criterion Covered Total %
statement 86 91 94.5
branch 27 32 84.3
condition 9 17 52.9
subroutine 17 18 94.4
pod 0 12 0.0
total 139 170 81.7


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2             #-------------------------------------------------------------------------------
3             # Binary search-able heap in 100% Pure Perl
4             # Philip R Brenan at gmail dot com, Appa Apps Ltd, 2017
5             #-------------------------------------------------------------------------------
6              
7             package Binary::Heap::Search;
8             require v5.16.0;
9 1     1   530 use warnings FATAL => qw(all);
  1         1  
  1         31  
10 1     1   3 use strict;
  1         1  
  1         14  
11 1     1   3 use Carp;
  1         1  
  1         60  
12 1     1   437 use Data::Dump qw(dump);
  1         5636  
  1         759  
13             our $VERSION = 2017.117;
14              
15             if (0) # Save to S3:- this will not work, unless you're me, or you happen, to know the key
16             {my $z = 'BinaryHeapSearch.zip';
17             print for qx(zip $z $0 && aws s3 cp $z s3://AppaAppsSourceVersions/$z && rm $z);
18             }
19              
20             #1 Methods
21             sub new($) # Create a new Binary Search-able Heap
22 23     23 0 20 {my ($compare) = @_; # Sub to perform <=> on two elements of the heap
23 23         424 return bless {compare=>$compare};
24             }
25              
26 288   100 288 0 942 sub arrays {$_[0]{arrays} //= []} ## Each array in the heap is in the order created by compare
27 265     265 0 282 sub compare {$_[0]{compare}} ## A sub that performs <=>/cmp on any two elements on the heap
28 0     0 0 0 sub size {scalar @{$_[0]->heaps}} ## Number of arrays in the heap
  0         0  
29              
30             sub mergeArrays($$$) ## Merge two ordered arrays to make a new ordered array
31 87     87 0 65 {my ($compare, $b, $c) = @_; # Sub to order elements, first array of elements to be merged, second array of elements to be merged
32 87         52 my @a;
33 87   66     208 while(@$b and @$c) # Sequentially merge the two arrays
34 256         4256 {my $k = $compare->($$b[0], $$c[0]); # Compare the smallest elements in each array
35 256 100       326 if ($k < 0) {push @a, shift @$b} # Save smallest element
  82 50       253  
36 174         520 elsif ($k > 0) {push @a, shift @$c}
37 0         0 else {confess "Duplicate entry ", dump($$b[0])}
38             }
39 87         367 @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
40             }
41              
42             sub mergeAdjacentArrays($$$) ## Merge adjacent arrays
43 122     122 0 113 {my ($arrays, $compare, $start) = @_; # Index of first array to be merged
44              
45 122         125 for my $small(reverse 1..$start) # Each array that might be merge-able
46 169         148 {my $b = $arrays->[$small-1]; # Larger array
47 169         112 my $c = $arrays->[$small-0]; # Smaller array
48 169 100 33     1047 if ($b and @$b and $c and @$c and @$b <= @$c * 2) # Adjacent arrays are close enough in size to warrant merging
      33        
      33        
      66        
49 87         98 {$arrays->[$small-1] = [mergeArrays($compare, $b, $c)];
50             }
51             else # Adjacent arrays are to different in size to be worth merging
52 82 100       115 {splice @$arrays, $small+1, $start-$small if $small != $start; # Remove previously merged arrays - this inefficient operation is done just once on a small array
53             return
54 82         1104 }
55             }
56 40         713 $#$arrays = 0; # All the arrays have been merged into just one array
57             }
58              
59             sub add($$) # Add an element to the heap of ordered arrays
60 203     203 0 204 {my ($heap, $element) = @_; # Heap, element (that can be ordered by compare)
61 203         197 my $compare = $heap->compare;
62 203         206 my $arrays = $heap->arrays;
63              
64 203         279 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 then merge up through all the arrays, doing so would avoid the splice operation in merge() but seems to produce longer sequences of arrays than the technique used which is to find the first viable array.
65 276         200 {my $array = $arrays->[$arrayIndex];
66 276         4576 my $c = $compare->($element, $array->[-1]); # Compare the element to be added to the topmost element of the current array
67 276 100       449 if ($c == 1) # The element to be added is greater than the largest element in the current array
    50          
68 85         108 {push @$array, $element; # Add the element to the top of this array
69 85 100       111 mergeAdjacentArrays($arrays, $compare, $arrayIndex) if $arrayIndex; # Merge two adjacent arrays if they are close enough in size
70 85         1575 return;
71             }
72             elsif ($c == 0) # Duplicate element detected
73 0         0 {confess "Duplicate element ", dump($element);
74             }
75             }
76 118         165 push @$arrays, [$element]; # Cannot put element on top of any array in the heap so create a new array
77 118 100       680 mergeAdjacentArrays($arrays, $compare, $#$arrays) if $#$arrays; # Try to merge the newest array if there is an existing array into which to merge it
78             }
79              
80             sub binarySearch($$$) ## Find an element in an array using binary search
81 67     67 0 73 {my ($array, $compare, $element) = @_; # Array, element
82 67         41 my $m = 0; # Check the lower bound of the array
83 67         55 my $e = $array->[$m]; # Lowest element in the array
84 67         1127 my $c = $compare->($element, $e); # Compare with lowest element in the array
85 67 100       95 return $e if $c == 0; # Equal to the lowest element
86 65 50       79 return undef unless $c == 1; # Lower than any element in the array
87 65         52 my $M = $#$array; # Check the upper bound of the array
88 65         54 my $E = $array->[$M]; # Highest element in the array
89 65         1053 my $C = $compare->($element, $E); # Compare with highest element in the array
90 65 100       111 return $E if $C == 0; # Equal to the highest element
91 63 50       76 return undef if $C == 1; # Lower than any element in the array
92              
93 63         86 while($m+1 < $M) # Narrow the zone
94 308         299 {my $i = int(($m+$M)/2); # Index of a point halfway between
95 308         232 my $e = $array->[$i]; # Element at mid point
96 308         6412 my $c = $compare->($element, $e); # Compare
97 308 100       422 return $e if $c == 0; # Found
98 250 100       439 ($c == 1 ? $m : $M) = $i;
99             } # Continue to narrow the range
100             undef # Not found
101 5         5 }
102              
103             sub find($$) # Find an element in the heap
104 62     62 0 61 {my ($heap, $element) = @_; # Heap, element (that can be ordered by compare)
105 62         72 my $compare = $heap->compare;
106 62         69 my $arrays = $heap->arrays;
107              
108 62         72 for my $array(@$arrays) # Use a binary search on each array in the heap
109 67         77 {my $e = binarySearch($array, $compare, $element);
110 67 100       183 return $e if defined $e # Return matching element
111             }
112             undef # Element not found
113 0         0 }
114              
115             # Test
116 1 50   1 0 495 sub test{eval join('', ) or die $@}
  1     1 0 27  
  1     23 0 3  
  1     23   551  
  1     1   11841  
  1         6  
  23         19  
  23         15  
  31         87  
  23         40  
  23         29  
  23         43  
  23         99  
  23         49  
  23         50  
  23         4207  
  1         91  
117              
118             test unless caller;
119              
120             # Documentation
121             #extractDocumentation() unless caller; # Extract the documentation
122              
123             1;
124              
125             =encoding utf-8
126              
127             =head1 Name
128              
129             Binary::Heap::Search - Binary search-able heap in 100% Pure Perl
130              
131             =head1 Synopsis
132              
133             =head1 Installation
134              
135             This module is written in 100% Pure Perl and is thus easy to read, modify and
136             install.
137              
138             Standard Module::Build process for building and installing modules:
139              
140             perl Build.PL
141             ./Build
142             ./Build test
143             ./Build install
144              
145             =head1 Author
146              
147             philiprbrenan@gmail.com
148              
149             http://www.appaapps.com
150              
151             =head1 Copyright
152              
153             Copyright (c) 2017 Philip R Brenan.
154              
155             This module is free software. It may be used, redistributed and/or modified
156             under the same terms as Perl itself.
157              
158             =cut
159              
160             __DATA__