File Coverage

blib/lib/Iterator/Merger.pm
Criterion Covered Total %
statement 157 166 94.5
branch 53 64 82.8
condition 24 29 82.7
subroutine 33 35 94.2
pod 2 2 100.0
total 269 296 90.8


line stmt bran cond sub pod time code
1             # build and eval the code to efficiently merge several iterators in one iterator
2             package Iterator::Merger;
3 4     4   72764 use strict;
  4         11  
  4         1015  
4 4     4   28 use warnings;
  4         10  
  4         1915  
5 4     4   30 use Carp;
  4         7  
  4         3664  
6 4     4   36 use base 'Exporter';
  4         10  
  4         7803  
7              
8             our $VERSION = '0.64';
9              
10             # use constant DEBUG => 1;
11              
12             our @EXPORT_OK = qw(
13             imerge
14             imerge_num
15             imerge_raw
16             );
17              
18             our %EXPORT_TAGS = (
19             all => \@EXPORT_OK
20             );
21              
22             our $Has_defined_or;
23             our $Has_array_heap;
24             our $Max_generate;
25              
26             $Has_defined_or = eval "undef // 1" unless defined $Has_defined_or;
27 4 100   4   1512 BEGIN { $Has_array_heap = eval "require Array::Heap;1" unless defined $Has_array_heap };
28             $Max_generate = $Has_array_heap ? 9 : 12 unless defined $Max_generate;
29              
30             my %Generator_cache;
31              
32 1 100 100 1   3 *imerge_raw = eval($Has_defined_or ?
  2384 50 100     391868  
  101597 100 66     2753982  
  101400 50       2066008  
  21283 100       97110  
  1470         2911  
  83932         270378  
  197         577  
  1893         324808  
  213         810  
  16         29  
  213         561  
  1865         4003  
  1864         3847  
  196         1138  
  102309         4441239  
  21435         450157  
  22934         60337  
  1666         3286  
33             q!sub {
34             # DEBUG && warn "defined or";
35             my @ites = @_ or return sub {};
36             if (@ites==1) {
37             my $ite = shift;
38             return ref($ite) eq 'GLOB' ? sub {scalar <$ite>} : sub {scalar &$ite};
39             }
40             for (@ites) {
41             if (ref($_) eq 'GLOB') {
42             my $fh = $_;
43             $_ = sub {<$fh>}
44             }
45             }
46             croak "arguments must be CODE references or filehandles" if grep {ref($_) ne 'CODE'} @ites;
47             my $ite = shift(@ites);
48             sub {
49             &$ite // do {
50             { # block for redo
51             $ite = shift(@ites) || return;
52             &$ite // redo
53             }
54             }
55             }
56             }!
57             :
58             q!sub {
59             # DEBUG && warn "temp var";
60             my @ites = @_ or return sub {};
61             if (@ites==1) {
62             my $ite = shift;
63             return ref($ite) eq 'GLOB' ? sub {scalar <$ite>} : sub {scalar &$ite};
64             }
65             for (@ites) {
66             if (ref($_) eq 'GLOB') {
67             my $fh = $_;
68             $_ = sub {<$fh>}
69             }
70             }
71             croak "arguments must be CODE references or filehandles" if grep {ref($_) ne 'CODE'} @ites;
72             my $ite = shift(@ites);
73             sub {
74             my $next = &$ite;
75             until (defined $next) {
76             $ite = shift(@ites) || return;
77             $next = &$ite;
78             }
79             $next
80             }
81             }!
82             ) || die $@;
83              
84             sub imerge {
85 646     450 1 807229 _imerge(1, 1, \@_)
86             }
87              
88             sub imerge_num {
89 450     450 1 1545970 _imerge(0, 1, \@_)
90             }
91              
92             sub _imerge {
93 900     900   2947 my ($lex, $asc, $iterators) = @_;
94 900         1934 my $nb = @$iterators;
95            
96 900 100       1993 croak "arguments must be CODE references or filehandles" if grep {ref($_) !~ /^CODE$|^GLOB$/} @$iterators;
  6736         20709  
97            
98 896 100       5453 if ($nb==0) {
    100          
    100          
99 48     4848   305 return sub {undef};
  4848         36571  
100             }
101             elsif ($nb==1) {
102             #return $iterators->[0];
103             # ensure scalar context
104 64         119 my $ite = $iterators->[0];
105 64 50   4564   425 return ref($ite) eq 'GLOB' ? sub {scalar <$ite>} : sub {scalar &$ite};
  0         0  
  9066         101979  
106             }
107             elsif ($nb <= $Max_generate) {
108             # DEBUG && warn "generate";
109 532 50       1305 if ($nb == grep {ref($_) eq 'GLOB'} @$iterators) {
  3388         6943  
110             # only globs
111 0   0     0 my $code = $Generator_cache{$nb, $lex, 1} ||= _merger_generator($nb, $lex, $asc, 1);
112 0         0 return $code->(@$iterators);
113             } else {
114 532         1507 for (@$iterators) {
115 3388 50       6716 if (ref($_) eq 'GLOB') {
116 0         0 my $fh = $_;
117 0     0   0 $_ = sub {<$fh>}
118 0         0 }
119             }
120 532   66     3969 my $code = $Generator_cache{$nb, $lex, 0} ||= _merger_generator($nb, $lex, $asc, 0);
121 532         16047 return $code->(@$iterators);
122             }
123             }
124             else {
125             # no generation, giveup on some ultimate optim: lets turn all GLOBs to CODEs...
126 252         810 for (@$iterators) {
127 3276 50       6133 if (ref($_) eq 'GLOB') {
128 0         0 my $fh = $_;
129 0     0   0 $_ = sub {<$fh>}
130 0         0 }
131             }
132 252 100       758 if ($Has_array_heap) {
133             # DEBUG && warn "heap";
134             # general case, use a heap
135 168         287 my @heap;
136             # cannot take references to *_heap_lex and *_heap functions,
137             # due to prototype problems...
138 168 100       412 if ($lex) {
139 84         164 for my $ite (@$iterators) {
140 1050         1798 my $val = &$ite;
141 1050 100       6447 Array::Heap::push_heap_lex(@heap, [$val, $ite]) if defined $val;
142             }
143             return sub {
144 62099   100 62099   755858 my $data = Array::Heap::pop_heap_lex(@heap) || return undef;
145 53615         77259 my $min = $data->[0];
146 53615 100       83586 if ( defined($data->[0] = $data->[1]->()) ) {
147 52583         266196 Array::Heap::push_heap_lex(@heap, $data);
148             }
149             $min
150 84         646 };
  53615         101924  
151             }
152             else {
153 84         182 for my $ite (@$iterators) {
154 1050         1822 my $val = &$ite;
155 1050 100       6259 Array::Heap::push_heap(@heap, [$val, $ite]) if defined $val;
156             }
157             return sub {
158 62965   100 62965   741483 my $data = Array::Heap::pop_heap(@heap) || return undef;
159 54481         77901 my $min = $data->[0];
160 54481 100       83308 if ( defined($data->[0] = $data->[1]->()) ) {
161 53449         262736 Array::Heap::push_heap(@heap, $data);
162             }
163             $min
164 84         685 };
  54481         94312  
165             }
166             }
167             else {
168             # DEBUG && warn "brutal";
169             # no heap available, lets be dirty
170 84         227 my @values = map {scalar &$_} @$iterators;
  1176         5349  
171             # warn "values: ", join(", ", map {length($_)?1:0} @values), "\n";
172 84 100       639 if ($lex) {
173             return sub {
174 33692     33692   411231 my $i=-1;
175 33692         44283 my $min;
176             my $min_i;
177 33692         49608 for (@values) {
178 473333         556547 ++$i;
179 473333 100 100     1384401 if (defined and ((not defined $min) or ($_ lt $min))) {
      100        
180 92650         118795 $min = $_;
181 92650         125595 $min_i = $i;
182             }
183             }
184 33692 100       74928 $values[$min_i] = $iterators->[$min_i]->() if defined $min_i;
185             # warn "value is ", (length($min)?1:0), " from $min_i";
186 33692         168129 $min
187 42         399 };
188             }
189             else {
190             return sub {
191 34044     34044   401448 my $i=-1;
192 34044         44083 my $min;
193             my $min_i;
194 34044         51342 for (@values) {
195 478261         560980 ++$i;
196 478261 100 100     1377061 if (defined and ((not defined $min) or ($_ < $min))) {
      100        
197 90971         112085 $min = $_;
198 90971         126141 $min_i = $i;
199             }
200             }
201 34044 100       77506 $values[$min_i] = $iterators->[$min_i]->() if defined $min_i;
202 34044         156582 $min
203 42         385 };
204             }
205             }
206             }
207             }
208              
209             # nb=10 => ~30KiB to eval (doubles each increment)
210             sub _merger_generator {
211 38     38   156 my ($nb, $lex, $asc, $globs) = @_;
212 38         95 my $str = "no warnings;sub{";
213 38         134 $str .= "my(". join(',', map {"\$i$_"} 1..$nb). ")=\@_;";
  242         579  
214 38 50       438 $str .= $globs ? "my\$n$_=<\$i$_>;" : "my\$n$_=&\$i$_;" for 1..$nb;
215 38         107 $str .= "my\$r;sub{";
216 38 50       187 my $cmp = $lex ? ($asc ? ' lt' : ' gt') : ($asc ? '<' : '>');
    50          
    100          
217 38         201 $str .= _cmp($cmp, $globs, 1..$nb);
218 38         402 $str .= ";\$r}}";
219              
220             # $str =~ s/;/;\n/g;
221             # $str =~ s/\$/ \$/g;
222             # $str =~ s/{/ {\n/g;
223             # $str =~ s/}/ }\n/g;
224             # warn "\n\n$str\n\n";
225            
226 38 50   2   4000 eval($str) || die "$@ in $str"
  2     2   20  
  2     2   6  
  2     2   291  
  2     2   19  
  2     2   4  
  2     2   375  
  2     2   24  
  2     2   5  
  2     2   552  
  2     2   23  
  2     2   6  
  2     2   697  
  2     2   22  
  2     2   5  
  2     2   1075  
  2         24  
  2         4  
  2         1828  
  2         54  
  2         10  
  2         3633  
  2         31  
  2         35  
  2         6134  
  2         23  
  2         5  
  2         5620  
  2         24  
  2         4  
  2         15072  
  2         22  
  2         5  
  2         37452  
  2         24  
  2         5  
  2         585  
  2         20  
  2         7  
  2         756  
  2         23  
  2         6  
  2         1218  
  2         22  
  2         5  
  2         2081  
  2         21  
  2         6  
  2         3885  
227             }
228              
229             # recursive comparison expression building
230             sub _cmp {
231 18378     18378   31505 my ($cmp, $globs, $i, $j) = splice(@_, 0, 4);
232 18378 50       54946 return $globs ? "(\$r=\$n$i,\$n$i=<\$i$i>)" : "(\$r=\$n$i,\$n$i=&\$i$i)" unless defined $j;
    100          
233 9170         19463 "(!defined\$n$j||defined\$n$i&&\$n$i$cmp\$n$j)?". _cmp($cmp, $globs, $i, @_). ":". _cmp($cmp, $globs, $j, @_)
234             }
235              
236             1