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__ |