File Coverage

lib/CSV/LINQ.pm
Criterion Covered Total %
statement 456 733 62.2
branch 137 330 41.5
condition 24 72 33.3
subroutine 74 108 68.5
pod 54 55 98.1
total 745 1298 57.4


line stmt bran cond sub pod time code
1             package CSV::LINQ;
2             ###############################################################################
3             # CSV::LINQ - LINQ-style query interface for CSV files
4             # Compatible: Perl 5.005_03 and later
5             # Platform : Windows / UNIX
6             ###############################################################################
7              
8 5     5   201059 use strict;
  5         11  
  5         375  
9 5 50 33 5   208 BEGIN { if ($] < 5.006 && !defined(&warnings::import)) { $INC{'warnings.pm'} = 'stub'; eval 'package warnings; sub import {}' } }
  0         0  
  0         0  
10 5     5   29 use warnings;
  5         15  
  5         447  
11             local $^W = 1;
12              
13 5 50   5   212 BEGIN { pop @INC if $INC[-1] eq '.' }
14              
15 5     5   43 use vars qw($VERSION $_fh_seq);
  5         9  
  5         889  
16             $VERSION = '1.00';
17             $VERSION = $VERSION;
18             $_fh_seq = 0;
19              
20             ###############################################################################
21             # Internal file-handle helper
22             ###############################################################################
23              
24             # _open_fh - open a file for reading ('<') or writing ('>') and return
25             # the glob name string. Works on Perl 5.005_03 and all later versions.
26             #
27             # Always uses a unique numbered package glob (CSV::LINQ::FH::H) so
28             # that concurrent iterators each get their own IO slot.
29             #
30             # $raw: if true, binmode is called (raw bytes).
31             # Pass 0 for CSV, where OS-level \r\n->\n conversion is desired.
32             sub _open_fh {
33 35     35   64 my($mode, $file, $raw) = @_;
34 35         34 $_fh_seq++;
35 35         39 my $seq = $_fh_seq;
36 35         73 my $fhn = "CSV::LINQ::FH::H${seq}";
37 35 100       74 my $arg = ($mode eq '>') ? ">$file" : "< $file";
38 5 50   5   36 { no strict 'refs'; open($fhn, $arg) or die "Cannot open '$file': $!\n" }
  5         12  
  5         489  
  35         39  
  35         2303  
39 5 50   5   29 if ($raw) { no strict 'refs'; binmode(*{$fhn}) }
  5         8  
  5         4185  
  35         121  
  0         0  
  0         0  
40 35         95 return $fhn;
41             }
42              
43             ###############################################################################
44             # Constructor
45             ###############################################################################
46              
47             sub new {
48 202     202 0 245 my($class, $iter) = @_;
49 202         1011 return bless { _iter => $iter }, $class;
50             }
51              
52             ###############################################################################
53             # CSV parsing (RFC 4180 compliant)
54             ###############################################################################
55              
56             sub _parse_csv_line {
57 103     103   254 my($line, $sep) = @_;
58 103 50       156 $sep = ',' unless defined $sep;
59 103         111 my @fields = ();
60 103         327 $line =~ s{\r\n\z|\r\z|\n\z}{};
61 103         118 my $pos = 0;
62 103         101 my $len = length($line);
63 103         165 while ($pos <= $len) {
64 286 100 100     632 if ($pos < $len && substr($line, $pos, 1) eq '"') {
65 4         7 $pos++;
66 4         5 my $field = '';
67 4         8 while ($pos < $len) {
68 46         59 my $c = substr($line, $pos, 1);
69 46 100       53 if ($c eq '"') {
70 6         9 $pos++;
71 6 100 100     38 if ($pos < $len && substr($line, $pos, 1) eq '"') {
72 2         3 $field .= '"';
73 2         2 $pos++;
74             }
75             else {
76 4         9 last;
77             }
78             }
79             else {
80 40         42 $field .= $c;
81 40         58 $pos++;
82             }
83             }
84 4         20 push @fields, $field;
85 4 100 66     20 $pos++ if $pos < $len && substr($line, $pos, 1) eq $sep;
86             }
87             else {
88 282         277 my $start = $pos;
89 282   100     613 while ($pos < $len && substr($line, $pos, 1) ne $sep) {
90 1003         1911 $pos++;
91             }
92 282         442 push @fields, substr($line, $start, $pos - $start);
93 282         365 $pos++;
94             }
95             }
96 103         260 return @fields;
97             }
98              
99             sub _format_csv_field {
100 78     78   120 my($value, $sep) = @_;
101 78 50       96 $sep = ',' unless defined $sep;
102 78 50       91 $value = '' unless defined $value;
103 78 50 33     228 if ($value =~ /["\n\r]/ || index($value, $sep) >= 0) {
104 0         0 $value =~ s/"/""/g;
105 0         0 return '"' . $value . '"';
106             }
107 78         188 return $value;
108             }
109              
110             ###############################################################################
111             # Data source methods
112             ###############################################################################
113              
114             sub From {
115 99     99 1 532009 my($class, $arrayref) = @_;
116 99 50       184 die "From() requires ARRAY reference\n"
117             unless ref($arrayref) eq 'ARRAY';
118 99         106 my $i = 0;
119             my $iter = sub {
120 505 100   505   422 return undef if $i >= scalar(@{$arrayref});
  505         698  
121 410         643 return $arrayref->[$i++];
122 99         226 };
123 99         167 return $class->new($iter);
124             }
125              
126             sub FromCSV {
127 27     27 1 143308 my($class, $file, %opts) = @_;
128 27 100       85 my $sep = defined $opts{sep} ? $opts{sep} : ',';
129 27         30 my $headers = $opts{headers};
130 27         32 my $skip = $opts{skip_header};
131 27         52 my $fhn = _open_fh('<', $file, 0);
132 27         43 my @cols = ();
133             {
134 5     5   44 no strict 'refs';
  5         9  
  5         715  
  27         34  
135 27 50       48 if (!defined $headers) {
136 27         37 my $hdr = readline(*{$fhn});
  27         342  
137 27 50       73 if (defined $hdr) {
138 27         43 @cols = _parse_csv_line($hdr, $sep);
139             }
140             }
141             else {
142 0         0 @cols = @{$headers};
  0         0  
143 0 0       0 if ($skip) {
144 0         0 readline(*{$fhn});
  0         0  
145             }
146             }
147             }
148             my $iter = sub {
149 5     5   32 no strict 'refs';
  5         56  
  5         16343  
150 98     98   94 my $line = readline(*{$fhn});
  98         603  
151 98 100       175 if (!defined $line) {
152 27         69 close(*{$fhn});
  27         241  
153 27         68 return undef;
154             }
155 71         250 $line =~ s{\r\n\z|\r\z|\n\z}{};
156 71 50       117 return undef if $line eq '';
157 71         100 my @vals = _parse_csv_line($line, $sep);
158 71         92 my %rec = ();
159 71         151 for my $i (0 .. $#cols) {
160 199         344 $rec{ $cols[$i] } = $vals[$i];
161             }
162 71         355 return { %rec };
163 27         156 };
164 27         99 return $class->new($iter);
165             }
166              
167             sub Range {
168 10     10 1 102 my($class, $start, $count) = @_;
169 10         10 my $current = $start;
170 10         9 my $end = $start + $count - 1;
171             my $iter = sub {
172 52 100   52   58 return undef if $current > $end;
173 45         58 return $current++;
174 10         18 };
175 10         12 return $class->new($iter);
176             }
177              
178             sub Empty {
179 0     0 1 0 my($class) = @_;
180 0     0   0 my $iter = sub { return undef };
  0         0  
181 0         0 return $class->new($iter);
182             }
183              
184             sub Repeat {
185 0     0 1 0 my($class, $element, $count) = @_;
186 0         0 my $i = 0;
187             my $iter = sub {
188 0 0   0   0 return undef if $i >= $count;
189 0         0 $i++;
190 0         0 return $element;
191 0         0 };
192 0         0 return $class->new($iter);
193             }
194              
195             ###############################################################################
196             # Internal iterator helpers
197             ###############################################################################
198              
199             sub _next {
200 787     787   942 my($self) = @_;
201 787         920 return $self->{_iter}->();
202             }
203              
204             ###############################################################################
205             # Filtering methods
206             ###############################################################################
207              
208             sub Where {
209 22     22 1 40 my($self, @args) = @_;
210 22         29 my $pred;
211 22 100       49 if (ref($args[0]) eq 'CODE') {
212 6         10 $pred = $args[0];
213             }
214             else {
215 16 50       35 die "Where() DSL requires even number of arguments\n"
216             if @args % 2 != 0;
217 16         36 my %cond = @args;
218             $pred = sub {
219 50     50   67 my $rec = $_[0];
220 50         80 for my $k (keys %cond) {
221 50 50       91 return 0 unless defined $rec->{$k};
222 50 100       135 return 0 unless $rec->{$k} eq $cond{$k};
223             }
224 32         95 return 1;
225 16         66 };
226             }
227 22         30 my $source = $self;
228             my $iter = sub {
229 71     71   103 while (1) {
230 103         165 my $elem = $source->_next();
231 103 100       183 return undef unless defined $elem;
232 82 100       116 return $elem if $pred->($elem);
233             }
234 22         52 };
235 22         53 return ref($self)->new($iter);
236             }
237              
238             ###############################################################################
239             # Projection methods
240             ###############################################################################
241              
242             sub Select {
243 21     21 1 27 my($self, $selector) = @_;
244 21         25 my $source = $self;
245             my $iter = sub {
246 131     131   147 my $elem = $source->_next();
247 131 100       161 return undef unless defined $elem;
248 110         136 return $selector->($elem);
249 21         36 };
250 21         41 return ref($self)->new($iter);
251             }
252              
253             sub SelectMany {
254 3     3 1 4 my($self, $selector) = @_;
255 3         3 my $source = $self;
256 3         2 my @buf = ();
257             my $iter = sub {
258 18     18   14 while (1) {
259 27 100       27 if (@buf) {
260 15         20 return shift @buf;
261             }
262 12         12 my $elem = $source->_next();
263 12 100       17 return undef unless defined $elem;
264 9         11 my $arr = $selector->($elem);
265 9 50       16 die "SelectMany: selector must return an ARRAY reference\n"
266             unless ref($arr) eq 'ARRAY';
267 9         8 push @buf, @{$arr};
  9         9  
268             }
269 3         6 };
270 3         4 return ref($self)->new($iter);
271             }
272              
273             ###############################################################################
274             # Concatenation methods
275             ###############################################################################
276              
277             sub Concat {
278 2     2 1 4 my($self, $second) = @_;
279 2         2 my $source = $self;
280 2         0 my $second2 = $second;
281 2         3 my $first_done = 0;
282             my $iter = sub {
283 14 100   14   15 unless ($first_done) {
284 8         9 my $elem = $source->_next();
285 8 100       10 if (defined $elem) {
286 6         9 return $elem;
287             }
288 2         2 $first_done = 1;
289             }
290 8         9 return $second2->_next();
291 2         4 };
292 2         4 return ref($self)->new($iter);
293             }
294              
295             sub Zip {
296 0     0 1 0 my($self, $second, $selector) = @_;
297 0         0 my $src1 = $self;
298 0         0 my $src2 = $second;
299             my $iter = sub {
300 0     0   0 my $e1 = $src1->_next();
301 0         0 my $e2 = $src2->_next();
302 0 0 0     0 return undef unless defined $e1 && defined $e2;
303 0         0 return $selector->($e1, $e2);
304 0         0 };
305 0         0 return ref($self)->new($iter);
306             }
307              
308             ###############################################################################
309             # Partitioning methods
310             ###############################################################################
311              
312             sub Take {
313 7     7 1 11 my($self, $count) = @_;
314 7 50       14 $count = 0 if $count < 0;
315 7         7 my $source = $self;
316 7         5 my $taken = 0;
317             my $iter = sub {
318 24 100   24   40 return undef if $taken >= $count;
319 18         23 my $elem = $source->_next();
320 18 100       22 return undef unless defined $elem;
321 17         16 $taken++;
322 17         18 return $elem;
323 7         15 };
324 7         12 return ref($self)->new($iter);
325             }
326              
327             sub Skip {
328 5     5 1 10 my($self, $count) = @_;
329 5 50       9 $count = 0 if $count < 0;
330 5         5 my $source = $self;
331 5         6 my $skipped = 0;
332             my $iter = sub {
333 14     14   25 while ($skipped < $count) {
334 13         16 my $elem = $source->_next();
335 13 50       17 return undef unless defined $elem;
336 13         19 $skipped++;
337             }
338 14         20 return $source->_next();
339 5         12 };
340 5         26 return ref($self)->new($iter);
341             }
342              
343             sub TakeWhile {
344 0     0 1 0 my($self, $pred) = @_;
345 0         0 my $source = $self;
346 0         0 my $done = 0;
347             my $iter = sub {
348 0 0   0   0 return undef if $done;
349 0         0 my $elem = $source->_next();
350 0 0       0 return undef unless defined $elem;
351 0 0       0 if ($pred->($elem)) {
352 0         0 return $elem;
353             }
354 0         0 $done = 1;
355 0         0 return undef;
356 0         0 };
357 0         0 return ref($self)->new($iter);
358             }
359              
360             sub SkipWhile {
361 0     0 1 0 my($self, $pred) = @_;
362 0         0 my $source = $self;
363 0         0 my $skipping = 1;
364             my $iter = sub {
365 0     0   0 while ($skipping) {
366 0         0 my $elem = $source->_next();
367 0 0       0 return undef unless defined $elem;
368 0 0       0 unless ($pred->($elem)) {
369 0         0 $skipping = 0;
370 0         0 return $elem;
371             }
372             }
373 0         0 return $source->_next();
374 0         0 };
375 0         0 return ref($self)->new($iter);
376             }
377              
378             ###############################################################################
379             # Ordering methods
380             ###############################################################################
381              
382             sub OrderBy {
383 16     16 1 24 my($self, $key_sel) = @_;
384 16         18 my @items = ();
385 16         41 while (defined(my $e = $self->_next())) { push @items, $e }
  89         103  
386 16         51 return CSV::LINQ::Ordered->_new_ordered([ @items ],
387             [{ sel => $key_sel, dir => 1, type => 'smart' }]);
388             }
389              
390             sub OrderByDescending {
391 1     1 1 3 my($self, $key_sel) = @_;
392 1         1 my @items = ();
393 1         3 while (defined(my $e = $self->_next())) { push @items, $e }
  7         9  
394 1         4 return CSV::LINQ::Ordered->_new_ordered([ @items ],
395             [{ sel => $key_sel, dir => -1, type => 'smart' }]);
396             }
397              
398             sub OrderByStr {
399 24     24 1 27 my($self, $key_sel) = @_;
400 24         26 my @items = ();
401 24         34 while (defined(my $e = $self->_next())) { push @items, $e }
  94         112  
402 24         83 return CSV::LINQ::Ordered->_new_ordered([ @items ],
403             [{ sel => $key_sel, dir => 1, type => 'str' }]);
404             }
405              
406             sub OrderByStrDescending {
407 5     5 1 6 my($self, $key_sel) = @_;
408 5         6 my @items = ();
409 5         6 while (defined(my $e = $self->_next())) { push @items, $e }
  15         17  
410 5         15 return CSV::LINQ::Ordered->_new_ordered([ @items ],
411             [{ sel => $key_sel, dir => -1, type => 'str' }]);
412             }
413              
414             sub OrderByNum {
415 16     16 1 22 my($self, $key_sel) = @_;
416 16         18 my @items = ();
417 16         28 while (defined(my $e = $self->_next())) { push @items, $e }
  72         88  
418 16         60 return CSV::LINQ::Ordered->_new_ordered([ @items ],
419             [{ sel => $key_sel, dir => 1, type => 'num' }]);
420             }
421              
422             sub OrderByNumDescending {
423 5     5 1 6 my($self, $key_sel) = @_;
424 5         687 my @items = ();
425 5         9 while (defined(my $e = $self->_next())) { push @items, $e }
  18         25  
426 5         17 return CSV::LINQ::Ordered->_new_ordered([ @items ],
427             [{ sel => $key_sel, dir => -1, type => 'num' }]);
428             }
429              
430             sub Reverse {
431 0     0 1 0 my($self) = @_;
432 0         0 my @items = ();
433 0         0 while (defined(my $e = $self->_next())) { push @items, $e }
  0         0  
434 0         0 my @rev = reverse @items;
435 0         0 return ref($self)->new(ref($self)->From([ @rev ])->{_iter});
436             }
437              
438             ###############################################################################
439             # Internal sort helpers
440             ###############################################################################
441              
442             # _extract_key($raw_value, $type) - normalise one sort key
443             #
444             # Returns a scalar for 'num'/'str', or a two-element arrayref [flag, value]
445             # for 'smart':
446             # [0, $numeric_val] - key is numeric
447             # [1, $string_val ] - key is string
448             sub _extract_key {
449 459     459   929 my($val, $type) = @_;
450 459 100       520 $val = '' unless defined $val;
451 459 100       512 if ($type eq 'num') {
    100          
452 128 100 66     342 return defined($val) && length($val) ? $val + 0 : 0;
453             }
454             elsif ($type eq 'str') {
455 172         260 return "$val";
456             }
457             else {
458             # smart: detect whether value looks like a number
459 159         127 my $t = $val;
460 159         185 $t =~ s{^\s+}{};
461 159         158 $t =~ s{\s+$}{};
462 159 100       268 if ($t =~ /^[+-]?(?:\d+\.?\d*|\d*\.\d+)(?:[eE][+-]?\d+)?$/) {
463 8         17 return [0, $t + 0];
464             }
465             else {
466 151         250 return [1, "$val"];
467             }
468             }
469             }
470              
471             # _compare_keys($ka, $kb, $type) - compare two extracted keys
472             sub _compare_keys {
473 546     546   572 my($ka, $kb, $type) = @_;
474 546 100       671 if ($type eq 'num') {
    100          
475 152         1057 return $ka <=> $kb;
476             }
477             elsif ($type eq 'str') {
478 186         199 return $ka cmp $kb;
479             }
480             else {
481             # smart: both are [flag, value] arrayrefs
482 208         204 my $fa = $ka->[0]; my $va = $ka->[1];
  208         181  
483 208         171 my $fb = $kb->[0]; my $vb = $kb->[1];
  208         181  
484 208 100 66     400 if ($fa == 0 && $fb == 0) { return $va <=> $vb }
  7 50 33     12  
485 201         245 elsif ($fa == 1 && $fb == 1) { return $va cmp $vb }
486 0         0 else { return $fa <=> $fb }
487             }
488             }
489              
490             # _perform_sort($items_aref, $specs_aref) - stable multi-key sort
491             #
492             # Schwartzian Transform:
493             # 1. Decorate each element: [ orig_index, [key1..keyN], item ]
494             # 2. Sort by keys in sequence; original index as final tie-breaker (stability)
495             # 3. Undecorate
496             sub _perform_sort {
497 67     67   87 my($items, $specs) = @_;
498              
499             # Step 1: decorate
500             my @decorated = map {
501 295         246 my $idx = $_;
502 295         264 my $item = $items->[$idx];
503 295         271 my @keys = map { _extract_key($_->{sel}->($item), $_->{type}) } @{$specs};
  459         576  
  295         281  
504 295         519 [$idx, [ @keys ], $item]
505 67         82 } 0 .. $#{$items};
  67         103  
506              
507             # Step 2: sort
508             my @sorted_dec = sort {
509 67         129 my $r = 0;
  437         365  
510 437         414 for my $i (0 .. $#{$specs}) {
  437         492  
511 546         683 my $cmp = _compare_keys($a->[1][$i], $b->[1][$i], $specs->[$i]{type});
512 546 100       642 if ($specs->[$i]{dir} < 0) { $cmp = -$cmp }
  65         55  
513 546 100       597 if ($cmp != 0) { $r = $cmp; last }
  419         368  
  419         354  
514             }
515 437 100       581 $r != 0 ? $r : ($a->[0] <=> $b->[0]);
516             } @decorated;
517              
518             # Step 3: undecorate
519 67         73 return map { $_->[2] } @sorted_dec;
  295         446  
520             }
521              
522             ###############################################################################
523             # Ordered sub-class (ThenBy support)
524             ###############################################################################
525              
526             package CSV::LINQ::Ordered;
527 5     5   45 use vars qw(@ISA);
  5         10  
  5         22761  
528             @ISA = ('CSV::LINQ');
529              
530             # _new_ordered($items_aref, $specs_aref) - internal constructor
531             #
532             # $specs_aref is an arrayref of sort-spec hashrefs:
533             # { sel => $code_ref, # key selector: ($item) -> $key
534             # dir => 1 or -1, # 1 = ascending, -1 = descending
535             # type => 'smart'|'str'|'num' # comparison family
536             # }
537             #
538             # Uses _factory so each _next() call draws from a fresh sorted iterator,
539             # enabling re-iteration of Ordered objects.
540             sub _new_ordered {
541 97     97   119 my($class, $items, $specs) = @_;
542             return bless {
543             _items => $items,
544             _specs => $specs,
545             _factory => sub {
546 67     67   92 my @sorted = CSV::LINQ::_perform_sort($items, $specs);
547 67         66 my $i = 0;
548 67 100       162 return sub { $i < scalar(@sorted) ? $sorted[$i++] : undef };
  349         418  
549             },
550 97         1530 }, $class;
551             }
552              
553             # Override _next() to use _factory if available
554             # When the iterator signals end-of-sequence (undef), clear {_iter} so
555             # the next call to _next() rebuilds a fresh iterator from {_factory},
556             # enabling re-iteration of the same Ordered object.
557             sub _next {
558 510     510   480 my($self) = @_;
559 510 100       583 unless (exists $self->{_iter}) {
560 67         74 $self->{_iter} = $self->{_factory}->();
561             }
562 510         535 my $val = $self->{_iter}->();
563 510 100       780 unless (defined $val) {
564 91         185 delete $self->{_iter};
565             }
566 510         660 return $val;
567             }
568              
569             # _thenby - shared implementation for all ThenBy* variants
570             #
571             # Non-destructive: builds a new spec list and returns a new
572             # CSV::LINQ::Ordered object. The original object is unchanged.
573             sub _thenby {
574 30     30   36 my($self, $key_sel, $dir, $type) = @_;
575 30         33 my @new_specs = (@{$self->{_specs}}, { sel => $key_sel, dir => $dir, type => $type });
  30         61  
576 30         52 return CSV::LINQ::Ordered->_new_ordered($self->{_items}, [ @new_specs ]);
577             }
578              
579 12     12   23 sub ThenBy { my($s, $k)=@_; $s->_thenby($k, 1, 'smart') }
  12         36  
580 1     1   2 sub ThenByDescending { my($s, $k)=@_; $s->_thenby($k, -1, 'smart') }
  1         3  
581 9     9   16 sub ThenByStr { my($s, $k)=@_; $s->_thenby($k, 1, 'str') }
  9         17  
582 1     1   1 sub ThenByStrDescending { my($s, $k)=@_; $s->_thenby($k, -1, 'str') }
  1         3  
583 5     5   14 sub ThenByNum { my($s, $k)=@_; $s->_thenby($k, 1, 'num') }
  5         10  
584 2     2   4 sub ThenByNumDescending { my($s, $k)=@_; $s->_thenby($k, -1, 'num') }
  2         5  
585              
586             package CSV::LINQ;
587              
588              
589             ###############################################################################
590             # Grouping methods
591             ###############################################################################
592              
593             sub GroupBy {
594 2     2 1 4 my($self, $key_sel, $elem_sel) = @_;
595 2         2 my %groups = ();
596 2         2 my @keys = ();
597 2         3 while (defined(my $e = $self->_next())) {
598 6         8 my $k = $key_sel->($e);
599 6 50       13 $k = '' unless defined $k;
600 6 100       7 unless (exists $groups{$k}) {
601 4         5 push @keys, $k;
602 4         6 $groups{$k} = [];
603             }
604 6 50       9 if (defined $elem_sel) {
605 0         0 push @{ $groups{$k} }, $elem_sel->($e);
  0         0  
606             }
607             else {
608 6         4 push @{ $groups{$k} }, $e;
  6         10  
609             }
610             }
611 2         2 my @result = ();
612 2         3 for my $k (@keys) {
613 4         6 push @result, { Key => $k, Elements => $groups{$k} };
614             }
615 2         5 return ref($self)->From([ @result ]);
616             }
617              
618             ###############################################################################
619             # Set operations
620             ###############################################################################
621              
622             sub Distinct {
623 1     1 1 2 my($self, $comparer) = @_;
624 1         2 my $source = $self;
625 1         1 my %seen = ();
626             my $iter = sub {
627 4     4   5 while (1) {
628 7         8 my $elem = $source->_next();
629 7 100       8 return undef unless defined $elem;
630 6 50       8 my $key = defined $comparer ? $comparer->($elem) : $elem;
631 6 50       6 $key = '' unless defined $key;
632 6 100       8 next if $seen{$key};
633 3         4 $seen{$key} = 1;
634 3         4 return $elem;
635             }
636 1         3 };
637 1         2 return ref($self)->new($iter);
638             }
639              
640             sub Union {
641 0     0 1 0 my($self, $second, $comparer) = @_;
642 0         0 return $self->Concat($second)->Distinct($comparer);
643             }
644              
645             sub Intersect {
646 0     0 1 0 my($self, $second, $comparer) = @_;
647 0         0 my %in2 = ();
648 0         0 while (defined(my $e = $second->_next())) {
649 0 0       0 my $k = defined $comparer ? $comparer->($e) : $e;
650 0 0       0 $k = '' unless defined $k;
651 0         0 $in2{$k} = 1;
652             }
653 0         0 my $source = $self;
654 0         0 my %seen = ();
655             my $iter = sub {
656 0     0   0 while (1) {
657 0         0 my $elem = $source->_next();
658 0 0       0 return undef unless defined $elem;
659 0 0       0 my $k = defined $comparer ? $comparer->($elem) : $elem;
660 0 0       0 $k = '' unless defined $k;
661 0 0       0 next unless $in2{$k};
662 0 0       0 next if $seen{$k};
663 0         0 $seen{$k} = 1;
664 0         0 return $elem;
665             }
666 0         0 };
667 0         0 return ref($self)->new($iter);
668             }
669              
670             sub Except {
671 0     0 1 0 my($self, $second, $comparer) = @_;
672 0         0 my %in2 = ();
673 0         0 while (defined(my $e = $second->_next())) {
674 0 0       0 my $k = defined $comparer ? $comparer->($e) : $e;
675 0 0       0 $k = '' unless defined $k;
676 0         0 $in2{$k} = 1;
677             }
678 0         0 my $source = $self;
679 0         0 my %seen = ();
680             my $iter = sub {
681 0     0   0 while (1) {
682 0         0 my $elem = $source->_next();
683 0 0       0 return undef unless defined $elem;
684 0 0       0 my $k = defined $comparer ? $comparer->($elem) : $elem;
685 0 0       0 $k = '' unless defined $k;
686 0 0       0 next if $in2{$k};
687 0 0       0 next if $seen{$k};
688 0         0 $seen{$k} = 1;
689 0         0 return $elem;
690             }
691 0         0 };
692 0         0 return ref($self)->new($iter);
693             }
694              
695             ###############################################################################
696             # Join operations
697             ###############################################################################
698              
699             sub Join {
700 5     5 1 10 my($self, $inner, $outer_key, $inner_key, $result_sel) = @_;
701 5         9 my %lookup = ();
702 5         10 while (defined(my $e = $inner->_next())) {
703 10         20 my $k = $inner_key->($e);
704 10 50       36 $k = '' unless defined $k;
705 10 50       36 $lookup{$k} = [] unless exists $lookup{$k};
706 10         11 push @{ $lookup{$k} }, $e;
  10         26  
707             }
708 5         8 my $source = $self;
709 5         8 my @buf = ();
710             my $iter = sub {
711 17     17   16 while (1) {
712 29 100       77 if (@buf) {
713 12         23 return shift @buf;
714             }
715 17         22 my $outer = $source->_next();
716 17 100       28 return undef unless defined $outer;
717 12         20 my $k = $outer_key->($outer);
718 12 50       33 $k = '' unless defined $k;
719 12 50       19 next unless exists $lookup{$k};
720 12         12 for my $inner_elem (@{ $lookup{$k} }) {
  12         17  
721 12         29 push @buf, $result_sel->($outer, $inner_elem);
722             }
723             }
724 5         18 };
725 5         14 return ref($self)->new($iter);
726             }
727              
728             sub GroupJoin {
729 0     0 1 0 my($self, $inner, $outer_key, $inner_key, $result_sel) = @_;
730 0         0 my %lookup = ();
731 0         0 while (defined(my $e = $inner->_next())) {
732 0         0 my $k = $inner_key->($e);
733 0 0       0 $k = '' unless defined $k;
734 0 0       0 $lookup{$k} = [] unless exists $lookup{$k};
735 0         0 push @{ $lookup{$k} }, $e;
  0         0  
736             }
737 0         0 my $source = $self;
738             my $iter = sub {
739 0     0   0 my $outer = $source->_next();
740 0 0       0 return undef unless defined $outer;
741 0         0 my $k = $outer_key->($outer);
742 0 0       0 $k = '' unless defined $k;
743 0 0       0 my $group = exists $lookup{$k} ? $lookup{$k} : [];
744 0         0 my $inner_query = ref($self)->From([ @{$group} ]);
  0         0  
745 0         0 return $result_sel->($outer, $inner_query);
746 0         0 };
747 0         0 return ref($self)->new($iter);
748             }
749              
750             ###############################################################################
751             # Quantifier methods
752             ###############################################################################
753              
754             sub All {
755 2     2 1 3 my($self, $pred) = @_;
756 2         3 while (defined(my $e = $self->_next())) {
757 6 100       7 return 0 unless $pred->($e);
758             }
759 1         2 return 1;
760             }
761              
762             sub Any {
763 1     1 1 2 my($self, $pred) = @_;
764 1 50       3 if (defined $pred) {
765 1         1 while (defined(my $e = $self->_next())) {
766 5 100       6 return 1 if $pred->($e);
767             }
768 0         0 return 0;
769             }
770             else {
771 0 0       0 return defined($self->_next()) ? 1 : 0;
772             }
773             }
774              
775             sub Contains {
776 0     0 1 0 my($self, $value, $comparer) = @_;
777 0         0 while (defined(my $e = $self->_next())) {
778 0 0       0 if (defined $comparer) {
779 0 0       0 return 1 if $comparer->($e, $value);
780             }
781             else {
782 0 0 0     0 if (!defined $value) {
    0          
783 0 0       0 return 1 unless defined $e;
784             }
785             elsif (defined $e && $e eq $value) {
786 0         0 return 1;
787             }
788             }
789             }
790 0         0 return 0;
791             }
792              
793             sub SequenceEqual {
794 0     0 1 0 my($self, $second, $comparer) = @_;
795 0         0 while (1) {
796 0         0 my $e1 = $self->_next();
797 0         0 my $e2 = $second->_next();
798 0 0 0     0 if (!defined $e1 && !defined $e2) {
799 0         0 return 1;
800             }
801 0 0 0     0 return 0 if !defined $e1 || !defined $e2;
802 0 0       0 if (defined $comparer) {
803 0 0       0 return 0 unless $comparer->($e1, $e2);
804             }
805             else {
806 0 0       0 return 0 unless $e1 eq $e2;
807             }
808             }
809             }
810              
811             ###############################################################################
812             # Element access methods
813             ###############################################################################
814              
815             sub First {
816 3     3 1 9 my($self, $pred) = @_;
817 3         7 while (defined(my $e = $self->_next())) {
818 3 50 33     16 if (!defined $pred || $pred->($e)) {
819 3         13 return $e;
820             }
821             }
822 0 0       0 if (defined $pred) {
823 0         0 die "No element satisfies the condition\n";
824             }
825 0         0 die "Sequence contains no elements\n";
826             }
827              
828             sub FirstOrDefault {
829 0     0 1 0 my($self, $pred, $default) = @_;
830 0 0       0 if (ref($pred) eq 'CODE') {
831 0         0 while (defined(my $e = $self->_next())) {
832 0 0       0 return $e if $pred->($e);
833             }
834 0         0 return $default;
835             }
836             else {
837 0         0 $default = $pred;
838 0         0 my $e = $self->_next();
839 0 0       0 return defined $e ? $e : $default;
840             }
841             }
842              
843             sub Last {
844 3     3 1 6 my($self, $pred) = @_;
845 3         3 my $found;
846 3         4 my $has = 0;
847 3         9 while (defined(my $e = $self->_next())) {
848 20 50 33     31 if (!defined $pred || $pred->($e)) {
849 20         16 $found = $e;
850 20         23 $has = 1;
851             }
852             }
853 3 50       9 if ($has) {
854 3         8 return $found;
855             }
856 0 0       0 if (defined $pred) {
857 0         0 die "No element satisfies the condition\n";
858             }
859 0         0 die "Sequence contains no elements\n";
860             }
861              
862             sub LastOrDefault {
863 0     0 1 0 my($self, $pred) = @_;
864 0         0 my $found;
865 0         0 my $has = 0;
866 0         0 while (defined(my $e = $self->_next())) {
867 0 0 0     0 if (!defined $pred || $pred->($e)) {
868 0         0 $found = $e;
869 0         0 $has = 1;
870             }
871             }
872 0 0       0 return $has ? $found : undef;
873             }
874              
875             sub Single {
876 0     0 1 0 my($self, $pred) = @_;
877 0         0 my $found;
878 0         0 my $count = 0;
879 0         0 while (defined(my $e = $self->_next())) {
880 0 0 0     0 if (!defined $pred || $pred->($e)) {
881 0         0 $found = $e;
882 0         0 $count++;
883 0 0       0 die "Sequence contains more than one element\n"
884             if $count > 1;
885             }
886             }
887 0 0       0 die "Sequence contains no elements\n" if $count == 0;
888 0         0 return $found;
889             }
890              
891             sub SingleOrDefault {
892 0     0 1 0 my($self, $pred) = @_;
893 0         0 my $found;
894 0         0 my $count = 0;
895 0         0 while (defined(my $e = $self->_next())) {
896 0 0 0     0 if (!defined $pred || $pred->($e)) {
897 0         0 $found = $e;
898 0         0 $count++;
899 0 0       0 return undef if $count > 1;
900             }
901             }
902 0 0       0 return $count == 1 ? $found : undef;
903             }
904              
905             sub ElementAt {
906 0     0 1 0 my($self, $index) = @_;
907 0 0       0 die "ElementAt: index out of range\n" if $index < 0;
908 0         0 my $i = 0;
909 0         0 while (defined(my $e = $self->_next())) {
910 0 0       0 return $e if $i == $index;
911 0         0 $i++;
912             }
913 0         0 die "ElementAt: index out of range\n";
914             }
915              
916             sub ElementAtOrDefault {
917 0     0 1 0 my($self, $index) = @_;
918 0 0       0 return undef if $index < 0;
919 0         0 my $i = 0;
920 0         0 while (defined(my $e = $self->_next())) {
921 0 0       0 return $e if $i == $index;
922 0         0 $i++;
923             }
924 0         0 return undef;
925             }
926              
927             ###############################################################################
928             # Aggregation methods
929             ###############################################################################
930              
931             sub Count {
932 3     3 1 8 my($self, $pred) = @_;
933 3         3 my $n = 0;
934 3         8 while (defined(my $e = $self->_next())) {
935 19 50 33     33 if (!defined $pred || $pred->($e)) {
936 19         33 $n++;
937             }
938             }
939 3         7 return $n;
940             }
941              
942             sub Sum {
943 1     1 1 2 my($self, $selector) = @_;
944 1         1 my $total = 0;
945 1         1 while (defined(my $e = $self->_next())) {
946 10 50       13 my $v = defined $selector ? $selector->($e) : $e;
947 10 50       21 $total += (defined $v ? $v : 0);
948             }
949 1         3 return $total;
950             }
951              
952             sub Min {
953 0     0 1 0 my($self, $selector) = @_;
954 0         0 my $min;
955 0         0 while (defined(my $e = $self->_next())) {
956 0 0       0 my $v = defined $selector ? $selector->($e) : $e;
957 0 0       0 next unless defined $v;
958 0 0 0     0 $min = $v if !defined $min || $v < $min;
959             }
960 0         0 return $min;
961             }
962              
963             sub Max {
964 0     0 1 0 my($self, $selector) = @_;
965 0         0 my $max;
966 0         0 while (defined(my $e = $self->_next())) {
967 0 0       0 my $v = defined $selector ? $selector->($e) : $e;
968 0 0       0 next unless defined $v;
969 0 0 0     0 $max = $v if !defined $max || $v > $max;
970             }
971 0         0 return $max;
972             }
973              
974             sub Average {
975 1     1 1 2 my($self, $selector) = @_;
976 1         1 my $sum = 0;
977 1         1 my $n = 0;
978 1         2 while (defined(my $e = $self->_next())) {
979 5 50       45 my $v = defined $selector ? $selector->($e) : $e;
980 5 50       15 $sum += (defined $v ? $v : 0);
981 5         8 $n++;
982             }
983 1 50       2 die "Sequence contains no elements\n" if $n == 0;
984 1         4 return $sum / $n;
985             }
986              
987             sub AverageOrDefault {
988 0     0 1 0 my($self, $selector) = @_;
989 0         0 my $sum = 0;
990 0         0 my $n = 0;
991 0         0 while (defined(my $e = $self->_next())) {
992 0 0       0 my $v = defined $selector ? $selector->($e) : $e;
993 0 0       0 $sum += (defined $v ? $v : 0);
994 0         0 $n++;
995             }
996 0 0       0 return $n == 0 ? undef : $sum / $n;
997             }
998              
999             sub Aggregate {
1000 0     0 1 0 my($self, @args) = @_;
1001 0         0 my($seed, $func, $result_sel);
1002 0 0       0 if (@args == 1) {
    0          
1003 0         0 $func = $args[0];
1004 0         0 my $first = $self->_next();
1005 0 0       0 return undef unless defined $first;
1006 0         0 $seed = $first;
1007             }
1008             elsif (@args == 2) {
1009 0         0 ($seed, $func) = @args;
1010             }
1011             else {
1012 0         0 ($seed, $func, $result_sel) = @args;
1013             }
1014 0         0 my $acc = $seed;
1015 0         0 while (defined(my $e = $self->_next())) {
1016 0         0 $acc = $func->($acc, $e);
1017             }
1018 0 0       0 return defined $result_sel ? $result_sel->($acc) : $acc;
1019             }
1020              
1021             ###############################################################################
1022             # Conversion methods
1023             ###############################################################################
1024              
1025             sub ToArray {
1026 101     101 1 152 my($self) = @_;
1027 101         114 my @result = ();
1028 101         136 while (defined(my $e = $self->_next())) {
1029 363         562 push @result, $e;
1030             }
1031 101         287 return @result;
1032             }
1033              
1034             sub ToList {
1035 0     0 1 0 my($self) = @_;
1036 0         0 my @result = ();
1037 0         0 while (defined(my $e = $self->_next())) {
1038 0         0 push @result, $e;
1039             }
1040 0         0 return [ @result ];
1041             }
1042              
1043             sub DefaultIfEmpty {
1044 0     0 1 0 my($self, $default) = @_;
1045 0         0 my $source = $self;
1046 0         0 my $started = 0;
1047 0         0 my $done = 0;
1048             my $iter = sub {
1049 0 0   0   0 if (!$started) {
1050 0         0 my $elem = $source->_next();
1051 0         0 $started = 1;
1052 0 0       0 unless (defined $elem) {
1053 0 0       0 unless ($done) {
1054 0         0 $done = 1;
1055 0         0 return $default;
1056             }
1057 0         0 return undef;
1058             }
1059 0         0 return $elem;
1060             }
1061 0 0       0 return undef if $done;
1062 0         0 return $source->_next();
1063 0         0 };
1064 0         0 return ref($self)->new($iter);
1065             }
1066              
1067             sub ToDictionary {
1068 2     2 1 3 my($self, $key_sel, $val_sel) = @_;
1069 2         3 my %dict = ();
1070 2         3 while (defined(my $e = $self->_next())) {
1071 4         5 my $k = $key_sel->($e);
1072 4 50       11 $k = '' unless defined $k;
1073 4 50       6 my $v = defined $val_sel ? $val_sel->($e) : $e;
1074 4         11 $dict{$k} = $v;
1075             }
1076 2         6 return { %dict };
1077             }
1078              
1079             sub ToLookup {
1080 2     2 1 13 my($self, $key_sel, $val_sel) = @_;
1081 2         3 my %lookup = ();
1082 2         3 my @keys = ();
1083 2         3 while (defined(my $e = $self->_next())) {
1084 6         9 my $k = $key_sel->($e);
1085 6 50       13 $k = '' unless defined $k;
1086 6 100       8 unless (exists $lookup{$k}) {
1087 4         38 push @keys, $k;
1088 4         7 $lookup{$k} = [];
1089             }
1090 6 50       8 my $v = defined $val_sel ? $val_sel->($e) : $e;
1091 6         12 push @{ $lookup{$k} }, $v;
  6         11  
1092             }
1093 2         12 return { %lookup };
1094             }
1095              
1096             sub ToCSV {
1097 8     8 1 19 my($self, $file, %opts) = @_;
1098 8 50       18 my $sep = defined $opts{sep} ? $opts{sep} : ',';
1099             my $headers = defined $opts{headers} ? $opts{headers}
1100             : defined $opts{label_order} ? $opts{label_order}
1101 8 100       20 : undef;
    100          
1102 8         1079 my $no_header = $opts{no_header};
1103 8         12 my @items = ();
1104 8         14 while (defined(my $e = $self->_next())) {
1105 18         28 push @items, $e;
1106             }
1107 8         16 my $fhn = _open_fh('>', $file, 0);
1108 8 50       17 unless ($no_header) {
1109 8         10 my @cols = ();
1110 8 100 33     34 if (defined $headers) {
    50          
1111 6         7 @cols = @{$headers};
  6         14  
1112             }
1113             elsif (@items && ref($items[0]) eq 'HASH') {
1114 2         3 @cols = sort keys %{ $items[0] };
  2         16  
1115             }
1116 8 50       12 if (@cols) {
1117 5     5   52 no strict 'refs';
  5         9  
  5         687  
1118 8         10 print {*{$fhn}}
  8         48  
1119 8         8 join($sep, map { _format_csv_field($_, $sep) } @cols) . "\n";
  24         33  
1120             }
1121 8 50 33     28 if (!@cols && @items && ref($items[0]) ne 'HASH') {
      33        
1122             # scalar sequence - no header
1123             }
1124             else {
1125 8         14 for my $item (@items) {
1126 5     5   35 no strict 'refs';
  5         9  
  5         763  
1127 18 50       42 if (ref($item) eq 'HASH') {
1128 18         16 print {*{$fhn}}
  18         46  
1129             join($sep, map {
1130 18         17 _format_csv_field($item->{$_}, $sep)
  54         66  
1131             } @cols) . "\n";
1132             }
1133             else {
1134 0         0 print {*{$fhn}}
  0         0  
  0         0  
1135             _format_csv_field($item, $sep) . "\n";
1136             }
1137             }
1138 5     5   33 no strict 'refs';
  5         12  
  5         372  
1139 8         9 close(*{$fhn});
  8         271  
1140 8         59 return 1;
1141             }
1142             }
1143 0           for my $item (@items) {
1144 5     5   29 no strict 'refs';
  5         10  
  5         670  
1145 0 0         if (ref($item) eq 'HASH') {
1146 0           my @cols = sort keys %{$item};
  0            
1147 0           print {*{$fhn}}
  0            
1148             join($sep, map {
1149 0           _format_csv_field($item->{$_}, $sep)
  0            
1150             } @cols) . "\n";
1151             }
1152             else {
1153 0           print {*{$fhn}}
  0            
  0            
1154             _format_csv_field($item, $sep) . "\n";
1155             }
1156             }
1157             {
1158 5     5   31 no strict 'refs';
  5         10  
  5         1169  
  0            
1159 0           close(*{$fhn});
  0            
1160             }
1161 0           return 1;
1162             }
1163              
1164             ###############################################################################
1165             # Utility methods
1166             ###############################################################################
1167              
1168             sub ForEach {
1169 0     0 1   my($self, $action) = @_;
1170 0           while (defined(my $e = $self->_next())) {
1171 0           $action->($e);
1172             }
1173 0           return;
1174             }
1175              
1176             1;
1177              
1178             __END__